def test_checkpath_dollarparenthesis(self): """ when $() is allowed, return 0 if path allowed """ args = self.args + ["--forbidden=[';', '&', '|','`','>','<', '${']"] userconf = CheckConfig(args).returnconf() shell = ShellCmd(userconf, args) INPUT = "echo $(echo aze)" return self.assertEqual(shell.check_path(INPUT), 0)
def test_checkpath_notallowed_path_completion(self): """ forbidden command, should return 1 """ args = self.args + ["--path=['/home', '/var']"] userconf = CheckConfig(args).returnconf() shell = ShellCmd(userconf, args) INPUT = "cd /tmp/" return self.assertEqual(shell.check_path(INPUT, completion=1), 1)
def test_checksecure_notallowed_command(self): """ forbidden command, should return 1 """ args = self.args + ["--allowed=['ls']"] userconf = CheckConfig(args).returnconf() shell = ShellCmd(userconf, args) INPUT = "ll" return self.assertEqual(shell.check_secure(INPUT), 1)
def test_checksecure_forbiddenchar(self): """ forbid character, should return 1 """ args = self.args + ["--forbidden=['l']"] userconf = CheckConfig(args).returnconf() shell = ShellCmd(userconf, args) INPUT = "ls" return self.assertEqual(shell.check_secure(INPUT), 1)
def test_checksecure_doublepipe(self): """ double pipes should be allowed, even if pipe is forbidden """ args = self.args + ["--forbidden=['|']"] userconf = CheckConfig(args).returnconf() shell = ShellCmd(userconf, args) INPUT = "ls || ls" return self.assertEqual(shell.check_secure(INPUT), 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 self.conf.has_key('ssh'): if os.environ.has_key('SSH_CLIENT') \ and not os.environ.has_key('SSH_TTY'): # 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') os.system(self.conf['ssh']) self.log.error('SFTP disconnect') sys.exit(0) else: self.log.error('*** forbidden SFTP connection') sys.exit(0) # initialise cli session from lshell.shellcmd import ShellCmd cli = ShellCmd(self.conf, None, None, None, None, \ self.conf['ssh']) if cli.check_path(self.conf['ssh'], None, ssh=1) == 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 self.conf.has_key('scpforce'): 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) os.system(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'] = get_aliases(self.conf['ssh'], \ self.conf['aliases']) # if command is not "secure", exit if cli.check_secure(self.conf['ssh'], strict=1, ssh=1): 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: os.system(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'])
class TestFunctions(unittest.TestCase): args = ['--config=%s/etc/lshell.conf' % TOPDIR, "--quiet=1"] userconf = CheckConfig(args).returnconf() shell = ShellCmd(userconf, args) 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_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_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_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_08_checkpath_notallowed_path(self): """ U08 | forbidden command, should return 1 """ args = self.args + ["--path=['/home', '/var']"] userconf = CheckConfig(args).returnconf() INPUT = "cd /tmp" return self.assertEqual(sec.check_path(INPUT, userconf)[0], 1) def test_09_checkpath_notallowed_path_completion(self): """ U09 | forbidden command, should return 1 """ args = self.args + ["--path=['/home', '/var']"] userconf = CheckConfig(args).returnconf() INPUT = "cd /tmp/" return self.assertEqual( sec.check_path(INPUT, userconf, completion=1)[0], 1) def test_10_checkpath_dollarparenthesis(self): """ U10 | when $() is allowed, return 0 if path allowed """ args = self.args + ["--forbidden=[';', '&', '|','`','>','<', '${']"] userconf = CheckConfig(args).returnconf() INPUT = "echo $(echo aze)" return self.assertEqual(sec.check_path(INPUT, userconf)[0], 0) def test_11_checkconfig_configoverwrite(self): """ U12 | forbid ';', then check_secure should return 1 """ args = ['--config=%s/etc/lshell.conf' % TOPDIR, '--strict=123'] userconf = CheckConfig(args).returnconf() return self.assertEqual(userconf['strict'], 123) def test_12_overssh(self): """ U12 | command over ssh """ args = self.args + ["--overssh=['exit']", '-c exit'] os.environ['SSH_CLIENT'] = '8.8.8.8 36000 22' if 'SSH_TTY' in os.environ: os.environ.pop('SSH_TTY') with self.assertRaises(SystemExit) as cm: CheckConfig(args).returnconf() return self.assertEqual(cm.exception.code, 0) def test_13_multiple_aliases_with_separator(self): """ U13 | multiple aliases using &&, || and ; separators """ # enable &, | and ; characters aliases = {'foo': 'foo -l', 'bar': 'open'} INPUT = "foo; fooo ;bar&&foo && foo | bar||bar || foo" return self.assertEqual( get_aliases(INPUT, aliases), ' foo -l; fooo ; open&& foo -l ' '&& foo -l | open|| open || foo -l') def test_14_sudo_all_commands_expansion(self): """ U14 | sudo_commands set to 'all' is equal to allowed variable """ args = self.args + ["--sudo_commands=all"] userconf = CheckConfig(args).returnconf() # exclude internal and sudo(8) commands exclude = builtins_list + ['sudo'] allowed = [x for x in userconf['allowed'] if x not in exclude] # sort lists to compare userconf['sudo_commands'].sort() allowed.sort() return self.assertEqual(allowed, userconf['sudo_commands']) def test_15_allowed_ld_preload_cmd(self): """ U15 | all allowed commands should be prepended with LD_PRELOAD """ args = self.args + ["--allowed=['echo','export']"] userconf = CheckConfig(args).returnconf() # sort lists to compare return self.assertEqual(userconf['aliases']['echo'], 'LD_PRELOAD=%s echo' % userconf['path_noexec']) def test_16_allowed_ld_preload_builtin(self): """ U16 | builtin commands should NOT be prepended with LD_PRELOAD """ args = self.args + ["--allowed=['echo','export']"] userconf = CheckConfig(args).returnconf() # verify that export is not automatically added to the aliases (i.e. # prepended with LD_PRELOAD) return self.assertNotIn('export', userconf['aliases']) def test_17_allowed_exec_cmd(self): """ U17 | allowed_shell_escape should NOT be prepended with LD_PRELOAD The command should not be added to the aliases variable """ args = self.args + ["--allowed_shell_escape=['echo']"] userconf = CheckConfig(args).returnconf() # sort lists to compare return self.assertNotIn('echo', userconf['aliases']) def test_18_forbidden_environment(self): """ U18 | unsafe environment are forbidden """ INPUT = 'export LD_PRELOAD=/lib64/ld-2.21.so' args = INPUT retcode = builtins.export(args)[0] return self.assertEqual(retcode, 1) def test_19_allowed_environment(self): """ U19 | other environment are accepted """ INPUT = 'export MY_PROJECT_VERSION=43' args = INPUT retcode = builtins.export(args)[0] return self.assertEqual(retcode, 0) def test_20_winscp_allowed_commands(self): """ U20 | when winscp is enabled, new allowed commands are automatically added (see man). """ args = self.args + ["--allowed=[]", "--winscp=1"] userconf = CheckConfig(args).returnconf() # sort lists to compare, except 'export' exclude = list(set(builtins_list) - set(['export'])) expected = exclude + [ 'scp', 'env', 'pwd', 'groups', 'unset', 'unalias' ] expected.sort() allowed = userconf['allowed'] allowed.sort() return self.assertEqual(allowed, expected) def test_21_winscp_allowed_semicolon(self): """ U21 | when winscp is enabled, use of semicolon is allowed """ args = self.args + ["--forbidden=[';']", "--winscp=1"] userconf = CheckConfig(args).returnconf() # sort lists to compare return self.assertNotIn(';', userconf['forbidden']) def test_22_prompt_short_0(self): """ U22 | short_prompt = 0 should show dir compared to home dir """ expected = '%s:~/foo$ ' % getuser() args = self.args + ['--prompt_short=0'] userconf = CheckConfig(args).returnconf() currentpath = "%s/foo" % userconf['home_path'] prompt = updateprompt(currentpath, userconf) # sort lists to compare return self.assertEqual(prompt, expected) def test_23_prompt_short_1(self): """ U23 | short_prompt = 1 should show only current dir """ expected = '%s: foo$ ' % getuser() args = self.args + ['--prompt_short=1'] userconf = CheckConfig(args).returnconf() currentpath = "%s/foo" % userconf['home_path'] prompt = updateprompt(currentpath, userconf) # sort lists to compare return self.assertEqual(prompt, expected) def test_24_prompt_short_2(self): """ U24 | short_prompt = 2 should show full dir path """ expected = '%s: %s$ ' % (getuser(), os.getcwd()) args = self.args + ['--prompt_short=2'] userconf = CheckConfig(args).returnconf() currentpath = "%s/foo" % userconf['home_path'] prompt = updateprompt(currentpath, userconf) # sort lists to compare return self.assertEqual(prompt, expected) def test_25_disable_ld_preload(self): """ U25 | empty path_noexec should disable LD_PRELOAD """ args = self.args + ["--allowed=['echo','export']", "--path_noexec=''"] userconf = CheckConfig(args).returnconf() # verify that no alias was created containing LD_PRELOAD return self.assertNotIn('echo', userconf['aliases']) def test_26_checksecure_quoted_command(self): """ U26 | quoted command should be parsed """ INPUT = 'echo 1 && "bash"' return self.assertEqual(sec.check_secure(INPUT, self.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 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)