def _build_cache(self, report_filename): """ Reads in the results of a past report, parses them, and builds a dict such as: { "192.168.1.50": {'port': 22, 'authname': 'myauthname'}, } """ cache = {} f = open(report_filename) reader = csv.DictReader(f) for row in reader: if row['ip'] == '' or row['auth.name'] == '': # Looks like we couldn't login to this machine last time. continue cache[row['ip']] = { 'port': int(row['port']), 'auth': row['auth.name'] } log.debug("Found cached results for: %s" % row['ip']) f.close() return cache
def main(self): (self.options, self.args) = self.parser.parse_args() # we dont need argv[0] in this list... self.args = self.args[1:] # Setup logging, this must happen early! setup_logging(self.options.log_file, self.options.log_level) log.debug("Running cli command: %s" % self.name) # Translate path to config file to something absolute and expanded: self.options.config = os.path.abspath( os.path.expanduser(self.options.config)) log.debug("Absolute config file: %s" % self.options.config) self._validate_options() if len(sys.argv) < 2: print(self.parser.error(_("Please enter at least 2 args"))) if RHO_PASSWORD in os.environ: log.info("Using passphrase from %s environment variable." % RHO_PASSWORD) self.passphrase = os.environ[RHO_PASSWORD] else: self.passphrase = getpass(_("Config Encryption Password:"******"ERROR: Name already exists: %s") % e.dupe_name sys.exit(1)
def main(self): (self.options, self.args) = self.parser.parse_args() # we dont need argv[0] in this list... self.args = self.args[1:] # Setup logging, this must happen early! setup_logging(self.options.log_file, self.options.log_level) log.debug("Running cli command: %s" % self.name) # Translate path to config file to something absolute and expanded: self.options.config = os.path.abspath(os.path.expanduser( self.options.config)) log.debug("Absolute config file: %s" % self.options.config) self._validate_options() if len(sys.argv) < 2: print(self.parser.error(_("Please enter at least 2 args"))) if RHO_PASSWORD in os.environ: log.info("Using passphrase from %s environment variable." % RHO_PASSWORD) self.passphrase = os.environ[RHO_PASSWORD] else: self.passphrase = getpass(_("Config Encryption Password:"******"ERROR: Name already exists: %s") % e.dupe_name sys.exit(1)
def read_file(filename, password): """ Decrypt contents of file with the given key, and return as a string. Assume that we're reading files that we encrypted. (i.e. we're not trying to read files encrypted manually with gpg) Also note that the password here is the user password, not the actual AES key. To get that we must read the first 8 bytes of the file to get the correct salt to use to convert the password to the key. Returns a tuple of (salt, json) """ if not os.path.exists(filename): raise NoSuchFileException() f = open(filename, 'r') # 2 byte version in hex ver = f.read(2) # 8 byte salt salt = f.read(8) # 16 byte initialization vector iv = f.read(16) log.debug("Read version: %s salt: %s iv: %s" % (ver, salt, iv)) cont = f.read() try: return_me = decrypt(cont, password, salt, iv) except Exception, e: log.warn("Exception while decrypting the configuration file: %s" % e) # raise raise DecryptionException
def _validate_options(self): CliCommand._validate_options(self) # We support two ways to specify profiles, with --profile=blah, or # without --profile= and just list profiles in the command. Hack here # to treat those raw args as --profiles so the subsequent code has just # one path. self.options.profiles.extend(self.args) hasRanges = len(self.options.ranges) > 0 hasProfiles = len(self.options.profiles) > 0 hasAuths = len(self.options.auth) > 0 hasHosts = len(self.options.hosts) > 0 hasReportFormat = len(self.options.reportformat) > 0 hasReport = len(self.options.report) > 0 hasReportFile = len(self.options.reportfile) > 0 if self.options.cachefile: self.options.cachefile = os.path.abspath(os.path.expanduser( self.options.cachefile)) log.debug("Using cached output: %s" % self.options.cachefile) if not os.path.exists(self.options.cachefile): self.parser.error(_("No such file: %s" % self.options.cachefile)) if hasRanges: self._validate_ranges(self.options.ranges) if not hasRanges and not hasProfiles and not self.options.showfields and not hasHosts: self.parser.print_help() sys.exit(1) if hasRanges and hasProfiles: self.parser.error(_("Cannot scan ranges and profiles at the same time.")) if self.options.username and hasAuths: self.parser.error(_("Cannot specify both --username and --auth")) if hasProfiles and hasAuths: self.parser.error(_("Cannot specify both auths and ranges.")) if hasRanges and not (self.options.username or hasAuths): self.parser.error(_( "--username or --auth required to scan a range.")) if hasReport: if hasReportFormat: self.parser.error(_( "Cannot specify both report-format and report.")) if hasReportFile: self.parser.error(_( "Cannot specify both report and output filename."))
def write_file(filename, plaintext, key): """ Encrypt plaintext with the given key and write to file. Existing file will be overwritten so be careful. """ f = open(filename, 'w') salt = os.urandom(8) iv = os.urandom(16) # a hex version string f.write("%2X" % CONFIG_VERSION) f.write(salt) f.write(iv) log.debug("version: %s salt: %s iv: %s" % (CONFIG_VERSION, salt, iv)) f.write(encrypt(plaintext, key, salt, iv)) f.close()
def _validate_options(self): CliCommand._validate_options(self) # We support two ways to specify profiles, with --profile=blah, or # without --profile= and just list profiles in the command. Hack here # to treat those raw args as --profiles so the subsequent code has just # one path. self.options.profiles.extend(self.args) hasRanges = len(self.options.ranges) > 0 hasProfiles = len(self.options.profiles) > 0 hasAuths = len(self.options.auth) > 0 if self.options.cachefile: self.options.cachefile = os.path.abspath( os.path.expanduser(self.options.cachefile)) log.debug("Using cached output: %s" % self.options.cachefile) if not os.path.exists(self.options.cachefile): self.parser.error( _("No such file: %s" % self.options.cachefile)) if hasRanges: self._validate_ranges(self.options.ranges) if not hasRanges and not hasProfiles and not self.options.showfields: self.parser.print_help() sys.exit(1) if hasRanges and hasProfiles: self.parser.error( _("Cannot scan ranges and profiles at the same time.")) if self.options.username and hasAuths: self.parser.error(_("Cannot specify both --username and --auth")) if hasProfiles and hasAuths: self.parser.error(_("Cannot specify both auths and ranges.")) if hasRanges and not (self.options.username or hasAuths): self.parser.error( _("--username or --auth required to scan a range."))
def _build_cache(self, report_filename): """ Reads in the results of a past report, parses them, and builds a dict such as: { "192.168.1.50": {'port': 22, 'authname': 'myauthname'}, } """ cache = {} f = open(report_filename) reader = csv.DictReader(f) for row in reader: if row['ip'] == '' or row['auth.name'] == '': # Looks like we couldn't login to this machine last time. continue cache[row['ip']] = {'port': int(row['port']), 'auth': row['auth.name']} log.debug("Found cached results for: %s" % row['ip']) f.close() return cache
def scan_profiles(self, profilenames): missing_profiles = [] ssh_job_list = [] for profilename in profilenames: profile = self.config.get_profile(profilename) if profile is None: missing_profiles.append(profilename) continue ips = [] for range_str in profile.ranges: ipr = rho_ips.RhoIpRange(range_str) ips.extend(ipr.list_ips()) for ip in ips: # Create a copy of the list of ports and authnames, # we're going to modify them if we have a cache hit: ports = list(profile.ports) authnames = list(profile.auth_names) # If a cache hit, move the port/auth to the start of the list: if ip in self.cache: log.debug("Cache hit for: %s" % ip) cached_port = self.cache[ip]['port'] log.debug("Cached port: %s %s" % (cached_port, type(cached_port))) cached_authname = self.cache[ip]['auth'] if cached_port in ports: ports.remove(cached_port) ports.insert(0, cached_port) log.debug("trying port %s first" % cached_port) if cached_authname in authnames: authnames.remove(cached_authname) authnames.insert(0, cached_authname) log.debug("trying auth %s first" % cached_authname) sshj = ssh_jobs.SshJob(ip=ip, ports=ports, auths=self._find_auths(authnames), rho_cmds=self.get_rho_cmds(), allow_agent=self.allow_agent) ssh_job_list.append(sshj) self.ssh_jobs.ssh_jobs = ssh_job_list self._run_scan() return missing_profiles
def connect(self, ssh_job): # do the actual paramiko ssh connection # Copy the list of ports, we'll modify it as we go: ports_to_try = list(ssh_job.ports) found_port = None # we'll set this once we identify a port that works found_auth = False while True: if found_auth: break if found_port != None: log.warn("Found ssh on %s:%s, but no auths worked." % (ssh_job.ip, found_port)) break if len(ports_to_try) == 0: log.debug("Could not find/connect to ssh on: %s" % ssh_job.ip) err = _("unable to connect") ssh_job.error = err break port = ports_to_try.pop(0) for auth in ssh_job.auths: ssh_job.error = None debug_str = "%s:%s/%s" % (ssh_job.ip, port, auth.name) # this checks the case of a passphrase we can't decrypt try: pkey = get_pkey(auth) except paramiko.SSHException, e: # paramiko throws an SSHException for pretty much everything... ;-< log.error("ssh key error for %s: %s" % (debug_str, str(e))) ssh_job.error = str(e) continue self.ssh = paramiko.SSHClient() self.ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) try: log.info("trying: %s" % debug_str) self.show_connect(ssh_job, port, auth) self.ssh.connect(ssh_job.ip, port=int(port), username=auth.username, password=auth.password, pkey=pkey, allow_agent=ssh_job.allow_agent, look_for_keys=ssh_job.look_for_keys, timeout=ssh_job.timeout) ssh_job.port = port ssh_job.auth = auth found_port = port found_auth = True log.info("success: %s" % debug_str) break # Implies we've found an SSH server listening: except paramiko.AuthenticationException, e: # Because we stop checking ports once we find one where ssh # is listening, we can report the error message here and it # will end up in the final report correctly: err = _("login failed") log.error(err) ssh_job.error = err found_port = port continue # No route to host: except socket.error, e: log.warn("No route to host, skipping port: %s" % debug_str) ssh_job.error = str(e) break
def connect(self, ssh_job): # do the actual paramiko ssh connection # Copy the list of ports, we'll modify it as we go: ports_to_try = list(ssh_job.ports) found_port = None # we'll set this once we identify a port that works found_auth = False while True: if found_auth: break if found_port is not None: log.warn("Found ssh on %s:%s, but no auths worked." % (ssh_job.ip, found_port)) break if len(ports_to_try) == 0: log.debug("Could not find/connect to ssh on: %s" % ssh_job.ip) err = _("unable to connect") ssh_job.error = err break port = ports_to_try.pop(0) for auth in ssh_job.auths: ssh_job.error = None debug_str = "%s:%s/%s" % (ssh_job.ip, port, auth.name) # this checks the case of a passphrase we can't decrypt try: pkey = get_pkey(auth) except paramiko.SSHException as e: # paramiko throws an SSHException for pretty much everything... ;-< log.error("ssh key error for %s: %s" % (debug_str, str(e))) ssh_job.error = str(e) continue self.ssh = paramiko.SSHClient() self.ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) try: log.info("trying: %s" % debug_str) self.show_connect(ssh_job, port, auth) self.ssh.connect(ssh_job.ip, port=int(port), username=auth.username, password=auth.password, pkey=pkey, allow_agent=ssh_job.allow_agent, look_for_keys=ssh_job.look_for_keys, timeout=ssh_job.timeout) ssh_job.port = port ssh_job.auth = auth found_port = port found_auth = True log.info("success: %s" % debug_str) break # Implies we've found an SSH server listening: except paramiko.AuthenticationException as e: # Because we stop checking ports once we find one where ssh # is listening, we can report the error message here and it # will end up in the final report correctly: err = _("login failed") log.error(err) ssh_job.error = err found_port = port continue # No route to host: except socket.error as e: log.warn("No route to host, skipping port: %s" % debug_str) ssh_job.error = str(e) break # TODO: Hitting a live port that isn't ssh will result in # paramiko.SSHException, do we need to handle this explicitly? # Something else happened: except Exception as detail: log.warn("Connection error: %s - %s" % (debug_str, str(detail))) ssh_job.error = str(detail) continue