yTNEF/Evolution TNEF Attachment decoder plugin directory traversal & buffer overflow vulnerabilities
Yorick Koster, June 2009
Abstract
yTNEF & the Evolution TNEF Attachment decoder plugin are affected by several directory traversal and buffer overflow vulnerabilities. The directory traversal vulnerabilities allow attackers to overwrite or create local files with the privileges of the target user. Exploiting the buffer overflow vulnerabilities allows for arbitrary code execution with the privileges of the target user.
See also
- #2009-013 yTNEF/Evolution TNEF attachment decoder input sanitization errors
- ytnef, evolution: TNEF attachment decoder input sanitization errors
- CVE-2009-3721
- CVE-2009-3887
Tested version
These vulnerabilities were discovered using the latest (stable) versions of Evolution (currently 2.62.2) and yTNEF (currently 2.6). The vulnerabilities were verified on the following Linux distributions:
- GNOME version of Mandriva Linux 2009 Spring running Evolution 2.26.1.1 (Evolution plugin installed by default)
- Ubuntu 9.04 running Evolution 2.26.1 (with evolution-plugins-experimental package installed)
Affected functions
The following functions are affected by these issues:
Evolution plugin:
* processTnef()
* saveVCard()
* saveVCalendar()
* saveVTask()
yTNEF:
* ProcessTNEF()
* SaveVCard()
* SaveVCalendar()
* SaveVTask()
Fix
There is currently no fix available.
Introduction
Transport Neutral Encapsulation Format (TNEF) is a proprietary e-mail attachment format used by Microsoft Outlook and Microsoft Exchange Server. A plugin for Evolution exists that provides basic support for TNEF encoded e-mails. This plugin uses the ytnef library (libytnef) for processing TNEF messages. It borrows code from the ytnef program, which is a program to work with procmail to decode TNEF streams (winmail.dat attachments). Both applications share (almost) code and are, because of this, both affected by the issues described in this document.
Evolution TNEF Attachment decoder plugin
The plugin is started on e-mail attachments that have a MIME type of either application/vnd.ms-tnef or application/ms-tnef. It creates a temporary directory under ~/.evolution/cache/tmp using the format tnef-attachment-XXXXXX. The TNEF attachment is saved as .evo-attachment.tnef.
void
org_gnome_format_tnef(void *ep, EMFormatHookTarget *t)
{
[...]
tmpdir = e_mkdtemp("tnef-attachment-XXXXXX");
if (tmpdir == NULL)
return;
filepath = tmpdir;
name = g_build_filename(tmpdir, ".evo-attachment.tnef", NULL);
out = camel_stream_fs_new_with_name(name, O_RDWR|O_CREAT, 0666);
The saved file is parsed by TNEFParseFile(), the result is stored in a struct of the type TNEFStruct. This struct is passed to the function processTnef(), which tries to extract all relevant data and attachments from the TNEF stream. Each relevant part of the TNEF stream is stored within the previously created temporary directory that are made available to the end user as separate e-mail attachments.
/* Extracting the winmail.dat */
TNEFInitialize(tnef);
tnef->Debug = verbose;
if (TNEFParseFile(name, tnef) == -1) {
printf("ERROR processing file\n");
}
processTnef(tnef);
TNEFFree(tnef);
/* Extraction done */
Figure 1: Evolution showing parsed TNEF stream containing two attachments.
yTNEF
yTNEF processes TNEF files in a similar manner. It receives a file name from the command line, calls TNEFParseFile() that creates a struct TNEFStruct after which ProcessTNEF() is called. If ProcessTNEF() finds attachments it can process, these attachments will be saved locally. The ProcessTNEF() function is almost the same as the processTnef() function of the Evolution plugin.
int main(int argc, char ** argv) {
[...]
for(i=1; i<argc; i++) {
[...]
TNEFInitialize(&TNEF);
TNEF.Debug = verbose;
if (TNEFParseFile(argv[ i], &TNEF) == -1) {
printf("ERROR processing file\n");
continue;
}
ProcessTNEF(TNEF);
TNEFFree(&TNEF);
}
}
Directory traversal
If a TNEF file is processed, both yTNEF and the Evolution plugin will save certain types of TNEF structures. Special processing functions are available for Contacts, Tasks & Appointments. These functions are called if the Message Class is set to a certain value.
void processTnef(TNEFStruct *tnef) {
[...]
/* First see if this requires special processing. */
/* ie: it's a Contact Card, Task, or Meeting request (vCal/vCard) */
if (tnef->messageClass[0] != 0) {
if (strcmp(tnef->messageClass, "IPM.Contact") == 0) {
saveVCard(tnef);
}
if (strcmp(tnef->messageClass, "IPM.Task") == 0) {
saveVTask(tnef);
}
if (strcmp(tnef->messageClass, "IPM.Appointment") == 0) {
saveVCalendar(tnef);
foundCal = 1;
}
}
if ((filename = MAPIFindUserProp(&(tnef->MapiProperties),
PROP_TAG(PT_STRING8,0x24))) != MAPI_UNDEFINED) {
if (strcmp(filename->data, "IPM.Appointment") == 0) {
/* If it's "indicated" twice, we don't want to save 2 calendar entries. */
if (foundCal == 0) {
saveVCalendar(tnef);
}
}
}
There is also code that treats TNEF structures with the Message Class set to IPM.Microsoft Mail.Note. In the Evolution plugin, this code is never called as the global variable saveRTF is set to zero. In case of yTNEF this global variable is controlled by the command line.
if (strcmp(TNEF.messageClass, "IPM.Microsoft Mail.Note") == 0) {
if ((saveRTF == 1) && (TNEF.subject.size > 0)) {
// Description
if ((filename=MAPIFindProperty(&(TNEF.MapiProperties),
PROP_TAG(PT_BINARY, PR_RTF_COMPRESSED)))
!= MAPI_UNDEFINED) {
[...]
After the structures mentioned before have been processed, all other attachments are also saved locally. The file names used to save the attachments are obtained from the TNEF data. In case of normal attachments, the code first looks if the TNEF data contains MAPI properties and if so, it will look for specific properties. If these exists, a file name is extracted from these properties. If the properties do not exist, the attachment's title is used. This title is also set through a TNEF structure. If this title is also not available, a default file name will be used instead.
if ((RealAttachment == 1) || (saveintermediate == 1)) {
/* Ok, it's not an embedded stream, so now we */
/* process it. */
if ((filename = MAPIFindProperty(&(p->MAPI),
PROP_TAG(30,0x3707)))
== MAPI_UNDEFINED) {
if ((filename = MAPIFindProperty(&(p->MAPI),
PROP_TAG(30,0x3001)))
== MAPI_UNDEFINED) {
filename = &(p->Title);
}
}
if (filename->size == 1) {
filename = (variableLength*)malloc(sizeof(variableLength));
filename->size = 20;
filename->data = (char*)malloc(20);
sprintf(filename->data, "file_%03i.dat", count);
}
if (filepath == NULL) {
sprintf(ifilename, "%s", filename->data);
} else {
sprintf(ifilename, "%s/%s", filepath, filename->data);
}
for(i=0; i<strlen(ifilename); i++)
if (ifilename[ i] == ' ')
ifilename[ i] = '_';
if ((fptr = fopen(ifilename, "wb"))==NULL) {
printf("ERROR: Error writing file to disk!");
} else {
if (object == 1) {
fwrite(filedata->data + 16,
sizeof(BYTE),
filedata->size - 16,
fptr);
} else {
fwrite(filedata->data,
sizeof(BYTE),
filedata->size,
fptr);
}
fclose(fptr);
}
}
Before a new file is created, all spaces within the file name are replaced with the underscore character. No additional sanitation is performed on the file name. Because of this, it is possible to traverse outside of the temporary directory and create or overwrite any file with the privileges of the target user. This allows an attacker to execute arbitrary code for example by overwriting ~/.bashrc.
Buffer overflow
Beside the directory traversal, it is also possible to trigger a buffer overflow by supplying an overly long file name. This is possible, because the file name is copied in a fixed size buffer (256 bytes). In the Evolution plugin, this triggers a buffer overflow on the heap. In case of yTNEF the file name is copied in a buffer on the stack, thus allowing for a stack-based buffer overflow to occur.
Evolution plugin:
void processTnef(TNEFStruct *tnef) {
[...]
ifilename = (char *) g_malloc(sizeof(char) * 256);
[...]
if (filepath == NULL) {
sprintf(ifilename, "%s", filename->data);
} else {
sprintf(ifilename, "%s/%s", filepath, filename->data);
}
yTNEF:
void ProcessTNEF(TNEFStruct TNEF) {
[...]
char ifilename[256];
[...]
if (filepath == NULL) {
sprintf(ifilename, "%s", filename->data);
} else {
sprintf(ifilename, "%s/%s", filepath, filename->data);
}
Proof of concept
The following Metasploit exploit module can be used to exploit one of the directory traversal vulnerabilities. In addition it can also be used to trigger one of the buffer overflow vulnerabilities that causes Evolution to crash.