3cd12006bb
Signed-off-by: Dean Troyer <dtroyer@gmail.com>
122 lines
4.5 KiB
Diff
122 lines
4.5 KiB
Diff
---
|
|
lshell/shellcmd.py | 80 +++++++++++++++++++++++++++++++++++++++++++++++++++--
|
|
1 file changed, 77 insertions(+), 3 deletions(-)
|
|
|
|
--- a/lshell/shellcmd.py
|
|
+++ b/lshell/shellcmd.py
|
|
@@ -30,7 +30,7 @@ import subprocess
|
|
|
|
from time import gmtime, strftime
|
|
from utils import get_aliases
|
|
-
|
|
+from distutils.spawn import find_executable
|
|
|
|
class ShellCmd(cmd.Cmd, object):
|
|
""" Main lshell CLI class
|
|
@@ -337,6 +337,44 @@ class ShellCmd(cmd.Cmd, object):
|
|
# strip all spaces/tabs
|
|
line = " ".join(line.split())
|
|
|
|
+ # Expand all variables
|
|
+ line = os.path.expandvars(line)
|
|
+
|
|
+ # *** AWK HOOK *** #
|
|
+ # Before we begin, check if user is trying
|
|
+ # to pass an awk script to the awk interpreter
|
|
+ # and disallow that option.
|
|
+ #
|
|
+ # Also disallow inline vars in awk since an attacker
|
|
+ # may use that to scramble a forbidden cmd
|
|
+ # such as the following shell escape:
|
|
+ # (awk -v X=ba -v Y=ash 'BEGIN { system("/bin/"X Y) }'
|
|
+ #
|
|
+ # In an ideal world we should parse the awk script
|
|
+ # and inline vars for forbidden paths and commands
|
|
+ # but that will require some gnarly regexes (esp for
|
|
+ # the inline vars). Deferring this as TODO
|
|
+ if re.match(r'\s*awk.*-f\s*[\w/~]+', line):
|
|
+ return self.warn_count('awk script option', oline, strict, ssh)
|
|
+ if re.match(r'\s*awk.*-v\s*\w+=', line):
|
|
+ return self.warn_count('awk inline variable option', oline, strict, ssh)
|
|
+
|
|
+
|
|
+ # process all quoted text seperately
|
|
+ # This logic is kept crudely simple on purpose.
|
|
+ # At most we might match the same stanza twice
|
|
+ # (for e.g. "'a'", 'a') but the converse would
|
|
+ # require detecting single quotation stanzas
|
|
+ # nested within double quotes and vice versa
|
|
+ relist = re.findall(r'[^=]\"(.+)\"',line)
|
|
+ relist2 = re.findall(r'[^=]\'(.+)\'',line)
|
|
+ relist = relist + relist2
|
|
+ for item in relist:
|
|
+ if self.check_secure(item, strict = strict):
|
|
+ return 1
|
|
+ if self.check_path(item, strict = strict):
|
|
+ return 1
|
|
+
|
|
# ignore quoted text
|
|
line = re.sub(r'\"(.+?)\"', '', line)
|
|
line = re.sub(r'\'(.+?)\'', '', line)
|
|
@@ -438,7 +476,8 @@ class ShellCmd(cmd.Cmd, object):
|
|
new_cmd_line = 'export ' + oline
|
|
self.g_line = new_cmd_line
|
|
self.check_secure(new_cmd_line, strict = strict)
|
|
- else:
|
|
+ # filter out macros, text or constructs that got picked up as commands
|
|
+ elif command.islower() and find_executable(command):
|
|
return self.warn_count('command', oline, strict, ssh, command)
|
|
return 0
|
|
|
|
@@ -499,6 +538,7 @@ class ShellCmd(cmd.Cmd, object):
|
|
%(self.conf['warning_counter']))
|
|
self.stderr.write('This incident has been reported.\n')
|
|
|
|
+
|
|
def check_path(self, line, completion=None, ssh=None, strict=None):
|
|
""" Check if a path is entered in the line. If so, it checks if user \
|
|
are allowed to see this path. If user is not allowed, it calls \
|
|
@@ -594,7 +634,41 @@ class ShellCmd(cmd.Cmd, object):
|
|
detect the new environment and then use that to update the \
|
|
environ of the lshell process.
|
|
"""
|
|
- pipe = subprocess.Popen("%s; env -0" % script,
|
|
+ try:
|
|
+ script_path = os.path.expanduser(script.\
|
|
+ strip("source").split()[0])
|
|
+ script_path = os.path.expandvars(script_path)
|
|
+ with open (script_path) as fd:
|
|
+ content = fd.readlines()
|
|
+ content = [line.strip('\n') for line in content]
|
|
+
|
|
+ # Although rare in a normal cases, an attacker
|
|
+ # may attempt to bypass line validation by
|
|
+ # scrambling commands via line continuations
|
|
+ partial_line = ""
|
|
+ for i,line in enumerate(content):
|
|
+ if line.startswith('#'):
|
|
+ continue
|
|
+ if len(line) > 1 and line.startswith('\\'):
|
|
+ # implying previous partial line
|
|
+ content[i] = line[:1].replace('\\', '', 1)
|
|
+ if partial_line:
|
|
+ content[i] = partial_line + line
|
|
+ if line.endswith('\\'):
|
|
+ # continuation character. First partial line.
|
|
+ # We shall expect the command to continue in
|
|
+ # a new line.
|
|
+ partial_line = content[i].strip('\\')
|
|
+ continue
|
|
+ partial_line = ""
|
|
+ if self.check_secure(content[i]):
|
|
+ return
|
|
+ if self.check_path(content[i]):
|
|
+ return
|
|
+ except:
|
|
+ pass
|
|
+
|
|
+ pipe = subprocess.Popen("%s; env -0" % script,
|
|
bufsize=1,
|
|
stdout=subprocess.PIPE,
|
|
shell=True)
|