Saturday, March 29, 2008

Wow, TFTP servers really do suck.

I went to a talk by Dave Aitel yesterday at Harvard (notes here, if you're interested). Hopefully more to say on it later, but it definitely helped my burnout by reminding me what I'm really doing in security. (Hint: poring through IDS alerts is not what I signed up for.)

One thing Dave mentioned is that TFTP servers are notoriously buggy, so I thought a nice exercise would be to take a look at one and see if I could find something. I'd been playing around in IDA Pro for a while and I was about sick of it, so I was ready to knock that off and read some actual source code. I went to SourceForge and downloaded the first TFTP server I saw. Sure enough, I had a remote DoS within probably 10 minutes. It can most likely even run code, though I'm not hugely interested in proving it right now. That might make a good rainy day project.

So, rather than go through a bunch of hassle, I just posted it on the bug tracker for the project. Was that bad? I kind of thought it was a tiny bit of code that nobody's probably using, but I just checked and it's had 60,000+ downloads.

If you're interested, the bug report is here. It's pretty simple, you need to successfully TFTP GET a file with a long enough filename to overflow the log message buffer. You can do that by requesting "./" a bunch of times before the filename you want. So, basically something like:
tftp X.X.X.X PUT `perl -e'print "A"x128'`
tftp X.X.X.X GET `perl -e'print "./"x100'`/`perl -e'print "A"x128'`
will cause the server to segfault.

UPDATE 4/3: Haha, oh I suck. Exploit code was posted for this bug a couple days after I made the initial bug report (http://www.offensive-security.com/0day/sourceforge-tftpd.py.txt) and
it's much simpler than I thought.

UPDATE AGAIN: Actually it looks like the code and advisories came out the week BEFORE I made the bug report. Now THAT is a weird coincidence, given that the vulnerable release of the server code has been sitting out there since June 2007. What are the odds? Anyway, original update continues...

Turns out the overflow happens well before I thought it did, and the logging function has nothing to do with it. I'm not sure what I failed to check that made me miss this. Actually, I think what I was doing was looking for a way specifically to overflow the log buffer, rather than the struct that holds the filename in the first place. I likely failed to check the simpler option of just sending a GET long filename at all because that wouldn't have gotten me to where I thought I needed to be in the code. I feel incredibly silly now. I guess in the future I will think harder about what I'm doing and pay more attention to detail.

Specifically, the processRequest function starts by defining two variables:
char logbuff[512];
request req;
The request struct looks like:
struct request
{
timeval tv;
fd_set readfds;
pthread_t threadId;
int m_socket;
BYTE sockInd;
BYTE attempt;
char path[256];
FILE *file;
char *filename;
char *mode;
char *alias;
...
And part of the processRequest function includes an unsafe:
strcpy(req.path, cfig.homes[0].target);
strcat(req.path, req.alias);
So, yeah. Next time just grep for strcpy and strcat.

No comments: