[LWN Logo]

Date:         Sat, 13 Nov 1999 09:54:29 -0800
From: Blue Boar <BlueBoar@THIEVCO.COM>
Subject:      thttpd 2.04 stack overflow (VD#6)
To: BUGTRAQ@SECURITYFOCUS.COM

-------------------------------------------------------------------
Periodically, the moderator of of the vuln-dev mailing list will post
summaries of issues discussed there to Bugtraq and possibly other relevant
lists.  This will usually happen when an issue has been resolved, or it
appears that there will be no further discussion on vuln-dev.  Each
separate issue will be given it's own posting to facilitate referencing
them separately, for discussion, forwarding, or appearance in vulnerability
databases.

To subscribe to vuln-dev, send an e-mail to listserv@securityfocus.com,
with SUBSCRIBE VULN-DEV in the body of the message.

A FAQ and archive can be found at www.securityfocus.com-->forums-->vuln-dev
(click on these sections, the web pages are forms-based.)
-------------------------------------------------------------------

Sorry to take so long getting this one out, especially after how quickly
the author had a fix.  Check out the times on the messages (My mail reader
calculates those out to be 5:05 P.M. and 5:58 P.M., my time).  I've been
wiped out for a few days with a nasty case of strep throat.  Anyway, I'm
including the two messages in their entirety.  If you run thttpd, you'll
want to take immediate action.

I commend the package author on his handling of this issue.  No whining, no
excuses, no attacking the person who found the holes, just a very timely
fix.

						BB
-------------------------------------------------------------------
Subject:     thttpd 2.04 stack overflow
   Date:     Wed, 10 Nov 1999 01:05:04 -0000
   From:     "D. J. Bernstein" <djb@CR.YP.TO>
     To:     VULN-DEV@SECURITYFOCUS.COM

thttpd is a single-process web server. According to Netcraft, it's used
on 1.82% of all HTTP servers, behind only Apache, IIS, Enterprise, and
Rapidsite. The current version is thttpd 2.04; as far as I know, the
comments below apply to all versions back to 1.90a.

The thttpd web page says that thttpd is simple, small, portable, fast,
and secure; it ``goes to great lengths to protect the web server machine
against attacks and breakins from other sites.'' Sounds good, doesn't it?

Today I glanced at the thttpd 2.04 source code, wondering how seriously
thttpd parsed HTTP If-Modified-Since fields. I was horrified to see that
tdate_parse() scans %[a-zA-Z] into a fixed-size stack buffer.

I tried running thttpd on a throwaway account, and feeding it an
If-Modified-Since line with 1300 x's. It dumped core. This is something
that any attacker on the Internet could do to any thttpd server, taking
down web service until thttpd is restarted.

Presumably, at least on little-endian machines, a careful attacker can
take over the thttpd server---i.e., take over web service, and anything
else running as ``nobody''---by overwriting only two or three bytes of
the return address. But I haven't spent any more time looking at the
code. Perhaps other people here would be interested in investigating
thttpd's security in more detail.

(Disclaimer: I'm writing my own HTTP server.)

---Dan

-------------------------------------------------------------------

Subject:     Re: thttpd 2.04 stack overflow
   Date:     Tue, 9 Nov 1999 17:58:45 -0800
   From:     Jef Poskanzer <jef@ACME.COM>
     To:     VULN-DEV@SECURITYFOCUS.COM

>Today I glanced at the thttpd 2.04 source code, wondering how seriously
>thttpd parsed HTTP If-Modified-Since fields. I was horrified to see that
>tdate_parse() scans %[a-zA-Z] into a fixed-size stack buffer.

You're right, that's pretty bad.  Thanks for the note.  Fortunately
the fix is trivial, and I had a new version of thttpd ready to go,
so I went ahead and released it.  The patch I applied is below, and
you can find the full tarchive at the usual place,
    http://www.acme.com/software/thttpd/
I was hoping to delay this release until I solve www.acme.com's current
bandwidth problems, but this is urgent enough to require an immediate fix.

By the way, this:

>According to Netcraft, it's used
>on 1.82% of all HTTP servers, behind only Apache, IIS, Enterprise, and
>Rapidsite.

is somewhat of an overstatement.  There are actually only a hundred or
so sites running thttpd.  One of them is Demon Internet, a British
company which serves over 100,000 domains on a single SGI box running
their modified version of thttpd.
---
Jef

         Jef Poskanzer  jef@acme.com  http://www.acme.com/jef/

*** tdate_parse.c       1999/09/15 16:09:36     1.1
--- tdate_parse.c       1999/11/10 01:16:39
***************
*** 211,217 ****
      */

      /* DD-mth-YY HH:MM:SS GMT */
!     if ( sscanf( cp, "%d-%[a-zA-Z]-%d %d:%d:%d GMT",
                &tm_mday, str_mon, &tm_year, &tm_hour, &tm_min,
                &tm_sec ) == 6 &&
            scan_mon( str_mon, &tm_mon ) )
--- 211,217 ----
      */

      /* DD-mth-YY HH:MM:SS GMT */
!     if ( sscanf( cp, "%d-%400[a-zA-Z]-%d %d:%d:%d GMT",
                &tm_mday, str_mon, &tm_year, &tm_hour, &tm_min,
                &tm_sec ) == 6 &&
            scan_mon( str_mon, &tm_mon ) )
***************
*** 225,231 ****
        }

      /* DD mth YY HH:MM:SS GMT */
!     else if ( sscanf( cp, "%d %[a-zA-Z] %d %d:%d:%d GMT",
                &tm_mday, str_mon, &tm_year, &tm_hour, &tm_min,
                &tm_sec) == 6 &&
            scan_mon( str_mon, &tm_mon ) )
--- 225,231 ----
        }

      /* DD mth YY HH:MM:SS GMT */
!     else if ( sscanf( cp, "%d %400[a-zA-Z] %d %d:%d:%d GMT",
                &tm_mday, str_mon, &tm_year, &tm_hour, &tm_min,
                &tm_sec) == 6 &&
            scan_mon( str_mon, &tm_mon ) )
***************
*** 239,245 ****
        }

      /* HH:MM:SS GMT DD-mth-YY */
!     else if ( sscanf( cp, "%d:%d:%d GMT %d-%[a-zA-Z]-%d",
                &tm_hour, &tm_min, &tm_sec, &tm_mday, str_mon,
                &tm_year ) == 6 &&
            scan_mon( str_mon, &tm_mon ) )
--- 239,245 ----
        }

      /* HH:MM:SS GMT DD-mth-YY */
!     else if ( sscanf( cp, "%d:%d:%d GMT %d-%400[a-zA-Z]-%d",
                &tm_hour, &tm_min, &tm_sec, &tm_mday, str_mon,
                &tm_year ) == 6 &&
            scan_mon( str_mon, &tm_mon ) )
***************
*** 253,259 ****
        }

      /* HH:MM:SS GMT DD mth YY */
!     else if ( sscanf( cp, "%d:%d:%d GMT %d %[a-zA-Z] %d",
                &tm_hour, &tm_min, &tm_sec, &tm_mday, str_mon,
                &tm_year ) == 6 &&
            scan_mon( str_mon, &tm_mon ) )
--- 253,259 ----
        }

      /* HH:MM:SS GMT DD mth YY */
!     else if ( sscanf( cp, "%d:%d:%d GMT %d %400[a-zA-Z] %d",
                &tm_hour, &tm_min, &tm_sec, &tm_mday, str_mon,
                &tm_year ) == 6 &&
            scan_mon( str_mon, &tm_mon ) )
***************
*** 267,273 ****
        }

      /* wdy, DD-mth-YY HH:MM:SS GMT */
!     else if ( sscanf( cp, "%[a-zA-Z], %d-%[a-zA-Z]-%d %d:%d:%d GMT",
                str_wday, &tm_mday, str_mon, &tm_year, &tm_hour, &tm_min,
                &tm_sec ) == 7 &&
            scan_wday( str_wday, &tm_wday ) &&
--- 267,273 ----
        }

      /* wdy, DD-mth-YY HH:MM:SS GMT */
!     else if ( sscanf( cp, "%400[a-zA-Z], %d-%400[a-zA-Z]-%d %d:%d:%d
GMT",
                str_wday, &tm_mday, str_mon, &tm_year, &tm_hour, &tm_min,
                &tm_sec ) == 7 &&
            scan_wday( str_wday, &tm_wday ) &&
***************
*** 283,289 ****
        }

      /* wdy, DD mth YY HH:MM:SS GMT */
!     else if ( sscanf( cp, "%[a-zA-Z], %d %[a-zA-Z] %d %d:%d:%d GMT",
                str_wday, &tm_mday, str_mon, &tm_year, &tm_hour, &tm_min,
                &tm_sec ) == 7 &&
            scan_wday( str_wday, &tm_wday ) &&
--- 283,289 ----
        }

      /* wdy, DD mth YY HH:MM:SS GMT */
!     else if ( sscanf( cp, "%400[a-zA-Z], %d %400[a-zA-Z] %d %d:%d:%d
GMT",
                str_wday, &tm_mday, str_mon, &tm_year, &tm_hour, &tm_min,
                &tm_sec ) == 7 &&
            scan_wday( str_wday, &tm_wday ) &&
***************
*** 299,305 ****
        }

      /* wdy mth DD HH:MM:SS GMT YY */
!     else if ( sscanf( cp, "%[a-zA-Z] %[a-zA-Z] %d %d:%d:%d GMT %d",
                str_wday, str_mon, &tm_mday, &tm_hour, &tm_min, &tm_sec,
                &tm_year ) == 7 &&
            scan_wday( str_wday, &tm_wday ) &&
--- 299,305 ----
        }

      /* wdy mth DD HH:MM:SS GMT YY */
!     else if ( sscanf( cp, "%400[a-zA-Z] %400[a-zA-Z] %d %d:%d:%d GMT %d",
                str_wday, str_mon, &tm_mday, &tm_hour, &tm_min, &tm_sec,
                &tm_year ) == 7 &&
            scan_wday( str_wday, &tm_wday ) &&