def test_05_checksecure_forbiddenchar(self): """ U05 | forbid character, should return 1 """ args = self.args + ["--forbidden=['l']"] userconf = CheckConfig(args).returnconf() INPUT = "ls" return self.assertEqual(sec.check_secure(INPUT, userconf)[0], 1)
def test_28_checksecure_quoted_command(self): """ U28 | quoted command should be parsed """ INPUT = "echo'/1.sh'" return self.assertEqual(sec.check_secure(INPUT, self.userconf)[0], 1)
def test_04_checksecure_forbiddenpipe(self): """ U04 | forbid pipe, should return 1 """ args = self.args + ["--forbidden=['|']"] userconf = CheckConfig(args).returnconf() INPUT = "ls | ls" return self.assertEqual(sec.check_secure(INPUT, userconf)[0], 1)
def test_27_checksecure_quoted_command(self): """ U27 | quoted command should be parsed """ INPUT = '"bash" && echo 1' return self.assertEqual(sec.check_secure(INPUT, self.userconf)[0], 1)
def __getattr__(self, attr): """ This method actually takes care of all the called method that are not resolved (i.e not existing methods). It actually will simulate the existence of any method entered in the 'allowed' variable list. e.g. You just have to add 'uname' in list of allowed commands in the 'allowed' variable, and lshell will react as if you had added a do_uname in the ShellCmd class! """ # expand environment variables in command line self.g_cmd = os.path.expandvars(self.g_cmd) self.g_line = os.path.expandvars(self.g_line) self.g_arg = os.path.expandvars(self.g_arg) # in case the configuration file has been modified, reload it if self.conf['config_mtime'] != os.path.getmtime( self.conf['configfile']): from lshell.checkconfig import CheckConfig self.conf = CheckConfig(['--config', self.conf['configfile']], refresh=1).returnconf() self.conf['promptprint'] = utils.updateprompt(os.getcwd(), self.conf) self.log = self.conf['logpath'] if self.g_cmd in ['quit', 'exit', 'EOF']: self.log.error('Exited') if self.g_cmd == 'EOF': self.stdout.write('\n') if self.conf['disable_exit'] != 1: sys.exit(0) # check that commands/chars present in line are allowed/secure ret_check_secure, self.conf = sec.check_secure( self.g_line, self.conf, strict=self.conf['strict']) if ret_check_secure == 1: # see http://tldp.org/LDP/abs/html/exitcodes.html self.retcode = 126 return object.__getattribute__(self, attr) # check that path present in line are allowed/secure ret_check_path, self.conf = sec.check_path(self.g_line, self.conf, strict=self.conf['strict']) if ret_check_path == 1: # see http://tldp.org/LDP/abs/html/exitcodes.html self.retcode = 126 # in case request was sent by WinSCP, return error code has to be # sent via an specific echo command if self.conf['winscp'] and re.search('WinSCP: this is end-of-file', self.g_line): utils.exec_cmd('echo "WinSCP: this is end-of-file: %s"' % self.retcode) return object.__getattribute__(self, attr) if self.g_cmd in self.conf['allowed']: if self.conf['timer'] > 0: self.mytimer(0) self.g_arg = re.sub('^~$|^~/', '%s/' % self.conf['home_path'], self.g_arg) self.g_arg = re.sub(' ~/', ' %s/' % self.conf['home_path'], self.g_arg) # replace previous command exit code # in case multiple commands (using separators), only replace first # command. Regex replaces all occurrences of $?, before ;,&,| if re.search('[;&\|]', self.g_line): p = re.compile("(\s|^)(\$\?)([\s|$]?[;&|].*)") else: p = re.compile("(\s|^)(\$\?)(\s|$)") self.g_line = p.sub(r' %s \3' % self.retcode, self.g_line) if type(self.conf['aliases']) == dict: self.g_line = utils.get_aliases(self.g_line, self.conf['aliases']) self.log.info('CMD: "%s"' % self.g_line) if self.g_cmd == 'cd': # split cd <dir> and rest of command cmd_split = re.split(';|&&|&|\|\||\|', self.g_line, 1) # in case the are commands following cd, first change the # directory, then execute the command if len(cmd_split) == 2: directory, command = cmd_split # only keep cd's argument directory = directory.split('cd', 1)[1].strip() # change directory then, if success, execute the rest of # the cmd line self.retcode, self.conf = builtins.cd(directory, self.conf) if self.retcode == 0: self.retcode = utils.exec_cmd(command) else: # set directory to command line argument and change dir directory = self.g_arg self.retcode, self.conf = builtins.cd(directory, self.conf) # built-in lpath function: list all allowed path elif self.g_cmd == 'lpath': self.retcode = builtins.lpath(self.conf) # built-in lsudo function: list all allowed sudo commands elif self.g_cmd == 'lsudo': self.retcode = builtins.lsudo(self.conf) # built-in history function: print command history elif self.g_cmd == 'history': self.retcode = builtins.history(self.conf, self.log) # built-in export function elif self.g_cmd == 'export': self.retcode, var = builtins.export(self.g_line) if self.retcode == 1: self.log.critical("** forbidden environment variable '%s'" % var) # case 'cd' is in an alias e.g. {'toto':'cd /var/tmp'} elif self.g_line[0:2] == 'cd': self.g_cmd = self.g_line.split()[0] directory = ' '.join(self.g_line.split()[1:]) self.retcode, self.conf = builtins.cd(directory, self.conf) else: self.retcode = utils.exec_cmd(self.g_line) elif self.g_cmd not in ['', '?', 'help', None]: self.log.warn('INFO: unknown syntax -> "%s"' % self.g_line) self.stderr.write('*** unknown syntax: %s\n' % self.g_cmd) self.g_cmd, self.g_arg, self.g_line = ['', '', ''] if self.conf['timer'] > 0: self.mytimer(self.conf['timer']) return object.__getattribute__(self, attr)
def __getattr__(self, attr): """ This method actually takes care of all the called method that are not resolved (i.e not existing methods). It actually will simulate the existence of any method entered in the 'allowed' variable list. e.g. You just have to add 'uname' in list of allowed commands in the 'allowed' variable, and lshell will react as if you had added a do_uname in the ShellCmd class! """ # expand environment variables in command line self.g_cmd = os.path.expandvars(self.g_cmd) self.g_line = os.path.expandvars(self.g_line) self.g_arg = os.path.expandvars(self.g_arg) # in case the configuration file has been modified, reload it if self.conf['config_mtime'] != os.path.getmtime( self.conf['configfile']): from lshell.checkconfig import CheckConfig self.conf = CheckConfig(['--config', self.conf['configfile']], refresh=1).returnconf() self.conf['promptprint'] = utils.updateprompt( os.getcwd(), self.conf) self.log = self.conf['logpath'] if self.g_cmd in ['quit', 'exit', 'EOF']: self.log.error('Exited') if self.g_cmd == 'EOF': self.stdout.write('\n') if self.conf['disable_exit'] != 1: sys.exit(0) # check that commands/chars present in line are allowed/secure ret_check_secure, self.conf = sec.check_secure( self.g_line, self.conf, strict=self.conf['strict']) if ret_check_secure == 1: # see http://tldp.org/LDP/abs/html/exitcodes.html self.retcode = 126 return object.__getattribute__(self, attr) # check that path present in line are allowed/secure ret_check_path, self.conf = sec.check_path(self.g_line, self.conf, strict=self.conf['strict']) if ret_check_path == 1: # see http://tldp.org/LDP/abs/html/exitcodes.html self.retcode = 126 # in case request was sent by WinSCP, return error code has to be # sent via an specific echo command if self.conf['winscp'] and re.search('WinSCP: this is end-of-file', self.g_line): utils.exec_cmd('echo "WinSCP: this is end-of-file: %s"' % self.retcode) return object.__getattribute__(self, attr) if self.g_cmd in self.conf['allowed']: if self.conf['timer'] > 0: self.mytimer(0) self.g_arg = re.sub('^~$|^~/', '%s/' % self.conf['home_path'], self.g_arg) self.g_arg = re.sub(' ~/', ' %s/' % self.conf['home_path'], self.g_arg) # replace previous command exit code # in case multiple commands (using separators), only replace first # command. Regex replaces all occurrences of $?, before ;,&,| if re.search('[;&|]', self.g_line): p = re.compile("(\s|^)(\$\?)([\s|$]?[;&|].*)") else: p = re.compile("(\s|^)(\$\?)(\s|$)") self.g_line = p.sub(r' %s \3' % self.retcode, self.g_line) if type(self.conf['aliases']) == dict: self.g_line = utils.get_aliases(self.g_line, self.conf['aliases']) self.log.info('CMD: "%s"' % self.g_line) if self.g_cmd == 'cd': # split cd <dir> and rest of command cmd_split = re.split(';|&&|&|\|\||\|', self.g_line, 1) # in case the are commands following cd, first change the # directory, then execute the command if len(cmd_split) == 2: directory, command = cmd_split # only keep cd's argument directory = directory.split('cd', 1)[1].strip() # change directory then, if success, execute the rest of # the cmd line self.retcode, self.conf = builtins.cd(directory, self.conf) if self.retcode == 0: self.retcode = utils.exec_cmd(command) else: # set directory to command line argument and change dir directory = self.g_arg self.retcode, self.conf = builtins.cd(directory, self.conf) # built-in lpath function: list all allowed path elif self.g_cmd == 'lpath': self.retcode = builtins.lpath(self.conf) # built-in lsudo function: list all allowed sudo commands elif self.g_cmd == 'lsudo': self.retcode = builtins.lsudo(self.conf) # built-in history function: print command history elif self.g_cmd == 'history': self.retcode = builtins.history(self.conf, self.log) # built-in export function elif self.g_cmd == 'export': self.retcode, var = builtins.export(self.g_line) if self.retcode == 1: self.log.critical( "** forbidden environment variable '%s'" % var) # case 'cd' is in an alias e.g. {'toto':'cd /var/tmp'} elif self.g_line[0:2] == 'cd': self.g_cmd = self.g_line.split()[0] directory = ' '.join(self.g_line.split()[1:]) self.retcode, self.conf = builtins.cd(directory, self.conf) else: self.retcode = utils.exec_cmd(self.g_line) elif self.g_cmd not in ['', '?', 'help', None]: self.log.warn('INFO: unknown syntax -> "%s"' % self.g_line) self.stderr.write('*** unknown syntax: %s\n' % self.g_cmd) self.g_cmd, self.g_arg, self.g_line = ['', '', ''] if self.conf['timer'] > 0: self.mytimer(self.conf['timer']) return object.__getattribute__(self, attr)
def test_06_checksecure_sudo_command(self): """ U06 | quoted text should not be forbidden """ INPUT = "sudo ls" return self.assertEqual(sec.check_secure(INPUT, self.userconf)[0], 1)
def test_07_checksecure_notallowed_command(self): """ U07 | forbidden command, should return 1 """ args = self.args + ["--allowed=['ls']"] userconf = CheckConfig(args).returnconf() INPUT = "ll" return self.assertEqual(sec.check_secure(INPUT, userconf)[0], 1)
def test_03_checksecure_doublepipe(self): """ U03 | double pipes should be allowed, even if pipe is forbidden """ args = self.args + ["--forbidden=['|']"] userconf = CheckConfig(args).returnconf() INPUT = "ls || ls" return self.assertEqual(sec.check_secure(INPUT, userconf)[0], 0)
def test_02_checksecure_simplequote(self): """ U02 | quoted text should not be forbidden """ INPUT = "ls -E '1|2' tmp/test" return self.assertEqual(sec.check_secure(INPUT, self.userconf)[0], 0)
def test_01_checksecure_doublequote(self): """ U01 | quoted text should not be forbidden """ INPUT = 'ls -E "1|2" tmp/test' return self.assertEqual(sec.check_secure(INPUT, self.userconf)[0], 0)
def check_scp_sftp(self): """ This method checks if the user is trying to SCP a file onto the server. If this is the case, it checks if the user is allowed to use SCP or not, and acts as requested. : ) """ if 'ssh' in self.conf: if 'SSH_CLIENT' in os.environ \ and 'SSH_TTY' not in os.environ: # check if sftp is requested and allowed if 'sftp-server' in self.conf['ssh']: if self.conf['sftp'] is 1: self.log.error('SFTP connect') utils.exec_cmd(self.conf['ssh']) self.log.error('SFTP disconnect') sys.exit(0) else: self.log.error('*** forbidden SFTP connection') sys.exit(0) # initialize cli session from lshell.shellcmd import ShellCmd cli = ShellCmd(self.conf, None, None, None, None, self.conf['ssh']) ret_check_path, self.conf = sec.check_path(self.conf['ssh'], self.conf, ssh=1) if ret_check_path == 1: self.ssh_warn('path over SSH', self.conf['ssh']) # check if scp is requested and allowed if self.conf['ssh'].startswith('scp '): if self.conf['scp'] is 1 or 'scp' in self.conf['overssh']: if ' -f ' in self.conf['ssh']: # case scp download is allowed if self.conf['scp_download']: self.log.error('SCP: GET "%s"' % self.conf['ssh']) # case scp download is forbidden else: self.log.error('SCP: download forbidden: "%s"' % self.conf['ssh']) sys.exit(0) elif ' -t ' in self.conf['ssh']: # case scp upload is allowed if self.conf['scp_upload']: if 'scpforce' in self.conf: cmdsplit = self.conf['ssh'].split(' ') scppath = os.path.realpath(cmdsplit[-1]) forcedpath = os.path.realpath(self.conf ['scpforce']) if scppath != forcedpath: self.log.error('SCP: forced SCP ' 'directory: %s' % scppath) cmdsplit.pop(-1) cmdsplit.append(forcedpath) self.conf['ssh'] = string.join( cmdsplit) self.log.error('SCP: PUT "%s"' % self.conf['ssh']) # case scp upload is forbidden else: self.log.error('SCP: upload forbidden: "%s"' % self.conf['ssh']) sys.exit(0) utils.exec_cmd(self.conf['ssh']) self.log.error('SCP disconnect') sys.exit(0) else: self.ssh_warn('SCP connection', self.conf['ssh'], 'scp') # check if command is in allowed overssh commands elif self.conf['ssh']: # replace aliases self.conf['ssh'] = utils.get_aliases(self.conf['ssh'], self.conf['aliases']) # if command is not "secure", exit ret_check_secure, self.conf = sec.check_secure( self.conf['ssh'], self.conf, strict=1, ssh=1) if ret_check_secure: self.ssh_warn('char/command over SSH', self.conf['ssh']) # else self.log.error('Over SSH: "%s"' % self.conf['ssh']) # if command is "help" if self.conf['ssh'] == "help": cli.do_help(None) else: utils.exec_cmd(self.conf['ssh']) self.log.error('Exited') sys.exit(0) # else warn and log else: self.ssh_warn('command over SSH', self.conf['ssh']) else: # case of shell escapes self.ssh_warn('shell escape', self.conf['ssh'])
def check_scp_sftp(self): """ This method checks if the user is trying to SCP a file onto the server. If this is the case, it checks if the user is allowed to use SCP or not, and acts as requested. : ) """ if 'ssh' in self.conf: if 'SSH_CLIENT' in os.environ \ and 'SSH_TTY' not in os.environ: # check if sftp is requested and allowed if 'sftp-server' in self.conf['ssh']: if self.conf['sftp'] is 1: self.log.error('SFTP connect') utils.exec_cmd(self.conf['ssh']) self.log.error('SFTP disconnect') sys.exit(0) else: self.log.error('*** forbidden SFTP connection') sys.exit(0) # initialize cli session from lshell.shellcmd import ShellCmd cli = ShellCmd(self.conf, None, None, None, None, self.conf['ssh']) ret_check_path, self.conf = sec.check_path(self.conf['ssh'], self.conf, ssh=1) if ret_check_path == 1: self.ssh_warn('path over SSH', self.conf['ssh']) # check if scp is requested and allowed if self.conf['ssh'].startswith('scp '): if self.conf['scp'] is 1 or 'scp' in self.conf['overssh']: if ' -f ' in self.conf['ssh']: # case scp download is allowed if self.conf['scp_download']: self.log.error('SCP: GET "%s"' % self.conf['ssh']) # case scp download is forbidden else: self.log.error( 'SCP: download forbidden: "%s"' % self.conf['ssh']) sys.exit(0) elif ' -t ' in self.conf['ssh']: # case scp upload is allowed if self.conf['scp_upload']: if 'scpforce' in self.conf: cmdsplit = self.conf['ssh'].split(' ') scppath = os.path.realpath(cmdsplit[-1]) forcedpath = os.path.realpath( self.conf['scpforce']) if scppath != forcedpath: self.log.error('SCP: forced SCP ' 'directory: %s' % scppath) cmdsplit.pop(-1) cmdsplit.append(forcedpath) self.conf['ssh'] = string.join( cmdsplit) self.log.error('SCP: PUT "%s"' % self.conf['ssh']) # case scp upload is forbidden else: self.log.error('SCP: upload forbidden: "%s"' % self.conf['ssh']) sys.exit(0) utils.exec_cmd(self.conf['ssh']) self.log.error('SCP disconnect') sys.exit(0) else: self.ssh_warn('SCP connection', self.conf['ssh'], 'scp') # check if command is in allowed overssh commands elif self.conf['ssh']: # replace aliases self.conf['ssh'] = utils.get_aliases( self.conf['ssh'], self.conf['aliases']) # if command is not "secure", exit ret_check_secure, self.conf = sec.check_secure( self.conf['ssh'], self.conf, strict=1, ssh=1) if ret_check_secure: self.ssh_warn('char/command over SSH', self.conf['ssh']) # else self.log.error('Over SSH: "%s"' % self.conf['ssh']) # if command is "help" if self.conf['ssh'] == "help": cli.do_help(None) else: utils.exec_cmd(self.conf['ssh']) self.log.error('Exited') sys.exit(0) # else warn and log else: self.ssh_warn('command over SSH', self.conf['ssh']) else: # case of shell escapes self.ssh_warn('shell escape', self.conf['ssh'])