Last updated at Fri, 22 Sep 2017 15:51:39 GMT

The update from 2012-12-14 contains a new module that brings code execution on an authenticated Postgres database to Linux. Metasploit has had this capability on Windows for quite some time but it took community contributor midnitesnake to scratch this particular itch. There are two reasons I'd like to talk about this module. First, it's not an exploit in the traditional sense because the vulnerability it takes advantage of is not really a vulnerability. Postgres allows authenticated users to write files. It also allows authenticated users to create so-called "User Defined Functions," which can be native functions in a shared object file. This combination allows authenticated code execution by design -- my favorite kind of vulnerability. The second reason I think this module is important is that it uses a relatively new method for compiling a payload on the fly, similar to the Linux sock_sendpage kernel exploit.

First, the vulnerability. Postgres can store arbitrarily large binary data in what the documentation calls a "largeobject". This seems to be meant primarily for images for webservers, but the particulars of why they exist for high-performance webservers and databases are irrelevant when you just want a shell. So let us suffice it to say that you can put a .so file in the database and write it to disk. The next step is instructing Postgres to load up a UDF from the supplied .so, which causes it to call dlopen(3) on that file, do some sanity checks, and eventually register some exported functions as callable from SQL. Traditionally, this has been done with legitimate Postgres extensions that can, for example, run system commands. However, there's a simpler way to go from .so to code execution. Let's see how the payload does its work.

The entire C payload follows:

    int _exit(int);
    int printf(const char*, ...);
    int perror(const char*);
    void *mmap(int, int, int, int, int, int);
    void *memcpy(void *, const void *, int);
    int mprotect(void *, int, int);
    int fork();
    int unlink(const char *pathname);
    
    #define MAP_PRIVATE 2
    #define MAP_ANONYMOUS 32
    #define PROT_READ 1
    #define PROT_WRITE 2
    #define PROT_EXEC 4
    
    #define PAGESIZE 0x1000
    
    char shellcode[] = "#{shellcode}";
    
    void run_payload(void) __attribute__((constructor));
    
    void run_payload(void)
    {
        int (*fp)();
        fp = mmap(0, PAGESIZE, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, 0, 0);
        
        memcpy(fp, shellcode, sizeof(shellcode));
        if (mprotect(fp, PAGESIZE, PROT_READ|PROT_WRITE|PROT_EXEC)) {
            _exit(1);
        }
        if (!fork()) {
            fp();
        }
        
        unlink("#{filename}");
        return;
    }

Notice that the first thing this code does is define all the libc function prototypes we'll be using. Doing it this way instead of just #include-ing stdio.h and friends allows Metasm, the awesome pure ruby compiler/assembler, to compile code that uses libc functions without actually having to parse all the header files; Metasm will still figure out that you need to link libc.so and put that information in the ELF headers. The main advantage of this is drastically reduced compile time, with a secondary perk being that you don't have to have Linux headers if you're compiling on Windows and vice versa.

Since we're compiling this on the fly, we have a little extra knowledge that wouldn't usually be available at compile time -- the filename where our payload will be stored. That means we can immediately delete the file as soon as the shellcode process has been kicked off, making it a little harder to notice when the compromise happens and a little more difficult to track down after the fact.

Note that the shellcode is in a global variable which will end up in a non-executable section of the ELF. The same issue would arise if we made it a local variable. It would go in the stack which is also non-executable. Thus, we will have to mmap some memory and mark it read-write-execute (RWX). To understand the reason for requiring RWX memory we need some familiarity with how Metasploit encoders work.

A brief aside on encoders

As TheLightCosine mentioned in his post about Anti-Virus evasion, encoders are not meant to defeat AV. Their primary purpose is avoiding bad characters. One way encoders accomplish this is by xor'ing the payload with bytes that make all the bad characters into something acceptable. To decode the resulting bytes, we need a decoder stub, and that decoder stub needs to know the address of the payload it's decoding. To get the address, the decoder stub will use some technique for returning the value of the instruction pointer and figure out the offset from there. Once it's got the address, it steps over each byte of the payload, xor'ing it back to its original value before falling through to the now-decoded payload.

So the memory in which the payload resides must be writable for the decoder to modify it back to its original form, and it must be executable to run that code once it gets decoded back to normal. Thus, we need RWX memory.

Back to your regularly scheduled Postgres

The tricky bit here is the use of a constructor, denoted by __attribute__((constructor)). This tells the compiler to place a pointer to the given function in the .init_array section of the compiled ELF file. Whenever dlopen(3) opens a file, one of the things it does is look in that section for a list of functions to call before it returns. Note that constructors are run from inside libc by dlopen before it returns so Postgres (or indeed anything dlopen(3)ing a shared lib) has no idea what those constructor functions do or even that they exist. Because of this feature, we don't have to jump through all the hoops Postgres sets up for creating a UDF. All we have to do is tell Postgres to create a UDF using this .so file and it will happily load it up with dlopen, running our shellcode in the process.

The equivalent Windows code for this exploit used to use a UDF for executing system commands. As a result of playing with the port to Linux, it became obvious that Windows can take a similar approach. Fortunately some of the work was already done and Windows can use the existing dll template in data/templates to accomplish the same feats as compiling a dynamic shared object for Linux. The default template will spawn a new process, inject the payload into that, and run it. Again, since everything happens from DllMain, it doesn't matter that the DLL doesn't follow the proper Postgres APIs.

Conclusions

Authenticated code execution by design is the best kind of vulnerability. From the vendor's point of view, it isn't a bug, so it will never be patched. From the IDS's perspective, it looks exactly like a normal connection, from a normal user who is allowed to talk to the server. Being able to compile a non-standard custom payload gives module writers incredible flexibility for exploiting situations like this.