Skip to content

Commit

Permalink
[sec] prevent escaping lshell by using sudo_noexec.so (Closes #122)
Browse files Browse the repository at this point in the history
This commit adds the possibility to prevent shell escapes by using
the sudo_noexec.so shared-object.

If sudo(8) is installed and sudo_noexec.so is available, it will be
loaded before running every command, preventing it from running
further commands itself.
  • Loading branch information
Ignace Mouzannar authored and Ignace Mouzannar committed Feb 6, 2016
1 parent 349faa7 commit 571aac4
Show file tree
Hide file tree
Showing 4 changed files with 57 additions and 3 deletions.
5 changes: 5 additions & 0 deletions etc/lshell.conf
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ loglevel : 2

[default]
## a list of the allowed commands or 'all' to allow all commands in user's PATH
## if sudo(8) is installed and sudo_noexec.so is available, it will be loaded
## before running every command, preventing it from running further commands
## itself. Otherwise, beware of commands like vim/find/more/etc. that will allow
## users to execute code (e.g. /bin/sh) from within the application, thus easily
## escaping lshell
allowed : ['ls','echo','cd','ll']

## a list of forbidden character or commands -- deny vim, as it allows to escape lshell
Expand Down
38 changes: 37 additions & 1 deletion lshell/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@

import re
import subprocess
import sys
import platform
import os

try:
from os import urandom
Expand All @@ -36,6 +39,7 @@ def urandom(n):
_urandomfd.close()
return bytes

ld_preload = ''

def get_aliases(line, aliases):
""" Replace all configured aliases in the line
Expand Down Expand Up @@ -69,10 +73,42 @@ def get_aliases(line, aliases):
line = line.replace('%s%s' %(char, char), '%s' %char)
return line

def get_noexec(noexec=''):
""" Get the sudo noexec file path, if available. This will allow us to prevent
applications from executing random commands thus escaping the shell
"""

if sys.platform.startswith('freebsd'):
noexec = '/usr/local/libexec/sudo_noexec.so'
elif sys.platform.startswith('netbsd'):
noexec = '/usr/pkg/libexec/sudo_noexec.so'
elif sys.platform.startswith('linux'):
if platform.linux_distribution(full_distribution_name=0)[0] in ('centos',
'redhat'):
noexec = '/usr/libexec/sudo_noexec.so'

elif platform.linux_distribution(full_distribution_name=0)[0] in ('fedora'):
noexec = '/usr/libexec/sudo/sudo_noexec.so'
elif platform.linux_distribution(full_distribution_name=0)[0] in ('debian',
'ubuntu',
'SuSE'):
noexec = '/usr/lib/sudo/sudo_noexec.so'

if os.path.isfile(noexec):
return 'LD_PRELOAD=%s' % noexec
else:
return 'LD_PRELOAD='

def exec_cmd(cmd):
""" execute a command, locally catching the signals """

# set LD_PRELOAD= if not already set (do it only once)
global ld_preload
if not ld_preload:
ld_preload = get_noexec()

try:
retcode = subprocess.call("%s" % cmd, shell=True)
retcode = subprocess.call("%s %s" % (ld_preload, cmd), shell=True)
except KeyboardInterrupt:
# exit code for user terminated scripts is 130
retcode = 130
Expand Down
8 changes: 7 additions & 1 deletion man/lshell.1
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,13 @@ command aliases list (similar to bash's alias directive)
.TP
.I allowed
a list of the allowed commands or set to 'all' to allow all commands in user's \
PATH
PATH.

if sudo(8) is installed and sudo_noexec.so is available, it will be loaded
before running every command, preventing it from running further commands
itself. Otherwise, beware of commands like vim/find/more/etc. that will allow
users to execute code (e.g. /bin/sh) from within the application, thus easily
escaping lshell
.TP
.I allowed_cmd_path
a list of path; all executable files inside these path will be allowed
Expand Down
9 changes: 8 additions & 1 deletion test/test_unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import lshell
from lshell.shellcmd import ShellCmd, LshellTimeOut
from lshell.checkconfig import CheckConfig
from lshell.utils import get_aliases
from lshell.utils import get_aliases, get_noexec
import os

TOPDIR="%s/../" % os.path.dirname(os.path.realpath(__file__))
Expand Down Expand Up @@ -120,5 +120,12 @@ def test_sudo_all_commands_expansion(self):
allowed.sort()
return self.assertEqual(allowed, userconf['sudo_commands'])

def test_noexec_ld_preload(self):
""" LD_PRELOAD should not be empty when tested on known machines """
args = self.args
userconf = CheckConfig(args).returnconf()
ld_preload = get_noexec()
return self.assertIn('LD_PRELOAD=/usr',ld_preload)

if __name__ == "__main__":
unittest.main()

0 comments on commit 571aac4

Please sign in to comment.