提到“黑客”,很多人脑子里马上浮现的是电影里那种黑屏绿字、飞快敲键盘的画面。但我实际在做的事情,更接近一个拿了白纸黑字授权的“伦理黑客”——用Python写自动化脚本,在不影响业务的前提下帮客户提前发现系统里可以被利用的薄弱点。这篇文章就是我把一次从原理到代码落地的伦理黑客实战完整拆开的过程:为什么选Python、环境怎么搭、端口扫描和弱口令检测这类基础功能怎么写,以及最后怎么把零散脚本拼成一个能出报告的完整工具。
如果你正在学Python,又想找一个比爬虫更“硬核”的练习方向,或者你本身就是做安全测试的同行,想看看别人是怎么组织代码的,这篇文章应该都能给你点实际参考。我会尽量把每一步的思路讲清楚,而不是只扔一段能跑的代码。
1. 为什么我用Python做伦理黑客演练:语言生态与伦理边界
1.1 伦理黑客和普通黑客的根本区别
先说清楚一个概念:伦理黑客(Ethical Hacker)和黑帽黑客干的事在技术层面经常是一样的——探测端口、分析服务、尝试登录、寻找Web漏洞,但有一个决定性差异,就是授权和目的。
伦理黑客所有操作都必须在授权范围内进行。这个授权不只是“口头答应”,而是要落在书面上,明确写清楚允许测试的目标IP段、允许使用的测试方式、测试的时间窗口,以及万一出问题时的应急联系人。我从入行第一天起就被师傅反复叮嘱一句话:没有授权的扫描就是违法,哪怕你只是扫了一个端口。
所以在设计这篇文章的实战项目时,我全程都只拿本地虚拟机里的靶场做演示,不碰任何真实的外部系统。这也是我想强调的第一条原则:技术本身是中性的,关键在于你是否在边界内使用它。
1.2 Python在安全自动化里凭什么“通吃”
做安全测试可用的语言很多,C、Go、Ruby、PowerShell都能干,但在写自动化检测脚本这件事上,Python的生态优势太明显了。
- 开发速度快。很多检测逻辑本质上就是“发请求、等响应、做判断”,Python的语法表达这些非常自然,从想法到能跑的脚本可能只需要十几分钟。
- 第三方库覆盖全面。
socket和scapy负责网络层,requests处理HTTP,paramiko做SSH协议交互,nmap库还能把Nmap的扫描结果直接变成Python对象,省去解析文本的麻烦。 - 跨平台。同一个脚本在Windows和Linux上都能跑,对安全测试这种经常要切换环境的场景特别友好。
- 和学习曲线相关。Python上手快,对刚接触安全的新手来说,不会因为语言本身的复杂性而放弃理解核心原理。
1.3 本篇演练的边界设定
这个项目的定位是“从原理到代码落地”,所以我会控制篇幅,聚焦在三个最经典、也最容易讲透的检测模块上:
- 主机存活探测与端口扫描——解决“目标开放了哪些端口”的问题。
- 服务指纹识别(Banner Grab)——解决“端口背后跑的是什么服务”的问题。
- 弱口令检测与Web基础安全检测——解决“已知服务是否存在最基础的薄弱点”的问题。
最后再把它们统一封装成一个带命令行参数、能输出报告的完整工具。全程不涉及漏洞利用、木马植入或横向渗透,那些内容超过本文范围,也容易踩到合规红线。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 搭建一个“合法动手”的实验环境
2.1 Python环境与编辑器配置里的几个坑
既然标题里带了Python,环境这关必须过。我知道很多人卡在第一步就是Python环境装不明白,这里把最省心的路径说一下。
我推荐直接到Python官网下载3.10以上的稳定版本,安装时记得勾选“Add Python to PATH”,这个选项默认不勾,漏掉的话后面在命令行里敲python大概率提示找不到命令。装完之后打开终端验证一下:
bash复制python --version
能正常打印出版本号就说明解释器没问题了。接下来创建虚拟环境,这一步很多人偷懒跳过,结果后面项目一多依赖互相打架:
bash复制python -m venv ethack_env
在Windows下激活是ethack_env\Scripts\activate,Linux和macOS是source ethack_env/bin/activate。激活后命令行前面会出现(ethack_env)前缀,说明已经进入了隔离环境。
编辑器这块,VSCode和PyCharm我都长期用过,配置Python环境的思路是一样的:先选择解释器,再配置终端。VSCode里按Ctrl+Shift+P,输入“Python: Select Interpreter”,然后选中刚才创建的虚拟环境里的python.exe。PyCharm则在Settings -> Project -> Python Interpreter里选择同一路径。这里的关键经验是:项目环境必须指向虚拟环境,而不是系统全局Python,否则你用了一堆pip install装的包,VSCode里一运行还是提示ModuleNotFoundError。
2.2 靶场选择:到哪里合法地练手
环境配置好之后,下一步就是找一个允许你“随便打”的目标。真实网站绝对不能碰,但为了练习这个项目,有几种现成的合法选择:
| 类型 | 代表 | 说明 |
|---|---|---|
| 本地靶机镜像 | Metasploitable 2 | 一个刻意做成“充满漏洞”的Linux虚拟机,开箱即用,非常适合练端口扫描和弱口令检测 |
| Web漏洞靶场 | DVWA、WebGoat | 专门用于练习Web安全检测的PHP/Java应用,有不同难度等级 |
| 在线练习平台 | HackTheBox、TryHackMe | 全球有名的合法攻防练习平台,里面有大量可授权的目标机器 |
| 自建实验环境 | 自己的虚拟机 | 比如装一个未做加固的Ubuntu Server,自己安装配置各类服务 |
我这次用的是Metasploitable 2加一个自建的Web靶站。我在VirtualBox里给Metasploitable 2分配了NAT网络模式,主机和靶机之间能互通就行。启动后先ping确认通了,再继续往下走。
2.3 授权确认:动手前必须做的一件事
给虚拟靶机操作不需要授权,但你一旦想把这个流程迁移到任何真实的网络环境、公司内网或者客户系统,授权就是第一优先级。
我的习惯是列一份简洁的授权清单,每次测试前逐项确认:
- 测试目标是否在合同约定的范围内?
- 是否明确标注了“禁止触碰”的系统或主机?
- 测试时段是否避开业务高峰期?
- 是否有第二联系人在紧急情况下可以确认操作边界?
这些看起来和写代码无关,但对伦理黑客来说是先于代码的“第一行代码”。我在实际项目里见过不止一次,因为少确认了一个域名,测试脚本把旁边一台无关业务的生产机也扫了,结果对方运维直接找上门来。所以无论练习做了多少次,这个流程我都不会省。
3. 从原理到代码:自研端口扫描器与服务识别
3.1 端口扫描的原理:三次握手与connect
端口扫描的本质,是探测目标主机上某个TCP端口是否处于监听状态。当一个客户端尝试和目标端口建立连接时,TCP会经历经典的三次握手:客户端发SYN,目标端口如果开放就回复SYN-ACK,客户端再回ACK,连接建立。
基于这个原理,connect_ex方法做的事就是:发起一次完整的三次握手,如果握手成功说明端口开放,被拒绝或超时则说明端口关闭或被防火墙拦截。这是实现成本最低、最不容易出错的扫描方式,也是Python标准库socket直接支持的方式。缺点是会在目标机器的连接日志里留下记录,而且扫描速度偏慢。
但对一个练习性质的项目,connect_ex完全够用。它的用法也简单:
python复制import socket
def check_port(host, port, timeout=1.0):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(timeout)
result = sock.connect_ex((host, port))
sock.close()
return result == 0
connect_ex返回0表示连接成功,其他值表示失败。这里的timeout很关键,设太小容易漏报,设太大扫描耗时会翻倍,我一般取1秒作为默认值。
3.2 多线程并发扫描的实现
单个端口逐一遍历对少量端口没问题,但如果扫一个/24网段的几千个端口,串行方式会慢到让人怀疑人生。解决思路是并发——用线程池同时发起多个连接尝试。
Python的concurrent.futures库把线程池封装得很干净:
python复制from concurrent.futures import ThreadPoolExecutor
import socket
def scan_port(host, port):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(1.0)
try:
result = sock.connect_ex((host, port))
return port, result == 0
finally:
sock.close()
def scan_range(host, start_port, end_port):
open_ports = []
with ThreadPoolExecutor(max_workers=200) as executor:
futures = {executor.submit(scan_port, host, p): p for p in range(start_port, end_port + 1)}
for future in futures:
port, is_open = future.result()
if is_open:
open_ports.append(port)
return sorted(open_ports)
max_workers=200意味着最多同时有200个连接在跑,这对本地靶机来说绰绰有余,而且不会给目标造成太大压力。这里也引出一个实际的工程问题:并发数不是越多越好。线程数开太大,一方面本机文件描述符可能不够用,另一方面目标防火墙很容易触发SYN洪水检测,反而导致大量端口被标记为超时。实操中我一般用100到300之间的值,再高收益就不明显了。
我跑了一下,对Metasploitable 2这台靶机扫描1到65535全部端口,200并发大约40秒左右跑完,结果里能看到21、22、23、25、80、3306这些常见服务端口。这个速度对练习来说已经没问题了。
3.3 抓Banner识别服务指纹
能扫出端口只是第一步。同一个端口跑的服务可能完全不同——80端口可能是Nginx也可能是Apache,3306可能是MySQL也可能是MariaDB。这就需要抓取服务指纹来做初步识别。
最原始也最可靠的方式是Banner Grab:连接上服务端口后发送一个简单的探测请求,读取服务端返回的版本信息。比如很多FTP和SSH服务在客户端连上之后,会主动返回一条含版本号的欢迎信息。
python复制def grab_banner(host, port, timeout=3.0):
try:
sock = socket.create_connection((host, port), timeout=timeout)
if port in (80, 443, 8080):
sock.send(b"HEAD / HTTP/1.0\r\n\r\n")
else:
sock.send(b"\r\n")
banner = sock.recv(1024).decode(errors="ignore").strip()
sock.close()
return banner
except Exception:
return None
把前面扫出来的开放端口逐个传给这个函数,就能得到一串类似SSH-2.0-OpenSSH_5.1p1 Debian-7或者220 (vsFTPd 2.3.4)的标识。看到OpenSSH和vsFTPd这种关键词,服务的类型和大致版本范围基本就锁定了,后续再针对相应版本查公开漏洞信息就有的放矢。
很多新手觉得Banner抓取太“笨”,不如直接用Nmap的-sV。我的观点是:练习阶段恰恰要先用“笨办法”把底层逻辑走通,等你理解了原理,再用Nmap这类集成工具才有底气,否则你根本不知道-sV输出的那些信息是怎么来的。
4. 弱口令与Web基础安全检测:让Python替你反复试探
4.1 SSH弱口令批量检测
端口扫描显示22端口开放且跑的是OpenSSH,那就绕不开一个常规检查项:是否存在弱口令。这在真实渗透测试里也是最常见的突破口之一,因为总有人图省事把密码设置成admin、123456或者和用户名一样。
Python这边和SSH协议打交道的首选库是paramiko。它实现了SSH客户端协议,可以模拟用户登录的过程,然后根据是否抛异常来判断密码是否正确:
python复制import paramiko
def ssh_login(host, port, username, password):
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
try:
client.connect(host, port=port, username=username, password=password, timeout=5)
return True
except paramiko.AuthenticationException:
return False
except Exception:
return False
finally:
client.close()
有了这个单次校验函数,再准备一个常见的用户名和密码字典,循环组合就行了。要注意的是,真实项目里暴力破解必须特别谨慎,失败的登录尝试会触发账号锁定策略,也可能让安全设备告警。所以我在代码里加了两处保护:一是每次尝试之间加一个随机延时,二是限制总的尝试次数。
对Metasploitable 2这台靶机,我试了msfadmin/msfadmin这组默认密码,脚本返回True,这恰恰说明了弱口令问题在真实环境里有多普遍。
4.2 HTTP安全响应头检查
Web服务是另一个大块。80端口如果开着,基本上可以确定有HTTP服务。最简单的Web检查项之一,是看响应头里有没有携带关键的安全头。
原理是这样的:浏览器在处理来自服务器的响应时,会依赖一些特殊的HTTP头来做安全限制。比如Strict-Transport-Security强制浏览器走HTTPS,X-Content-Type-Options阻止浏览器猜测文件类型,X-Frame-Options防止页面被嵌入到非法iframe里。如果这些头缺失,页面虽然能正常显示,但攻击者可以利用这些缺失进行点击劫持、MIME嗅探等攻击。
python复制import requests
import urllib3
urllib3.disable_warnings()
SECURITY_HEADERS = [
"Strict-Transport-Security",
"Content-Security-Policy",
"X-Content-Type-Options",
"X-Frame-Options",
"Referrer-Policy",
]
def check_security_headers(url):
response = requests.get(url, timeout=5, verify=False)
headers = response.headers
missing = [header for header in SECURITY_HEADERS if header not in headers]
return response.status_code, dict(headers), missing
verify=False是因为很多练习靶站的HTTPS证书是自签的,不让requests做证书校验。这个函数返回的missing列表就是检测报告里最直观的一类发现。
4.3 目录扫描与注入点初探
Web应用常见的另一个弱点是暴露了敏感目录。我见过不少站点把后台管理入口放在/admin、/phpmyadmin这种路径下,还没有做任何访问控制。目录扫描的原理很简单:准备一个字典,逐个拼接URL去请求,看返回状态码是不是200或者有重定向。
python复制def scan_directories(base_url, wordlist):
found = []
for path in wordlist:
url = f"{base_url.rstrip('/')}/{path.strip()}"
try:
response = requests.get(url, timeout=3, verify=False)
if response.status_code in (200, 301, 302, 403):
found.append((path, response.status_code))
except requests.RequestException:
pass
return found
关于SQL注入点初探,我只做最基础的探测:在URL参数后面加上单引号或者1 AND 1=1这类payload,看响应内容里是否出现数据库报错关键字。如果出现SQL syntax或者mysql_fetch_array这类字样,就说明参数值被直接拼进了SQL语句,存在注入嫌疑。这一步的价值在于把“可疑参数”标记出来,交给后续人工分析。
这里必须强调:探测和利用是两回事。探测阶段对目标几乎没有影响,而进一步利用就可能造成数据泄露或数据库损坏。所以我宁可把脚本停在这一步,也不在练习过程中去“证明”漏洞有多严重。
5. 把零散模块拼成可复用的检测工具
5.1 工程化设计:入口、参数与返回结构
代码写到这个阶段,如果只是放在Jupyter Notebook里逐个单元格跑,对练习没问题,但要做到“代码落地”,就必须考虑工程化。我的原则很简单:一个脚本只做一件事,一个入口统一调度所有事。
我一般用argparse来解析命令行参数,这样工具才能自由指定目标、端口范围和字典路径。命令行入口大概是这个形态:
bash复制python ethack_cli.py --host 192.168.56.101 --ports 1-10000 --wordlist common.txt --threads 200
argparse的配置不需要太多代码,但它的价值在于让别人(包括三个月后的自己)不用读源码就知道怎么用这个工具。
每个检测模块都应该返回统一的数据结构。我的做法是让每个函数都返回一个字典或对象,至少包含三个字段:target(目标)、finding(发现内容)、severity(风险等级)。这样后面生成报告时就非常轻松,不用再写一堆if-else去处理不同的返回格式。
5.2 输出与报告:让扫描结果可沉淀
测试做完没有报告等于没做。对于安全测试这个场景,报告不仅是给客户看的交付物,也是后续复测的基线。
我把报告输出分成两个层次:控制台打印实时结果,文件输出完整报告。文件格式我选了JSON和HTML两种。JSON方便程序处理,HTML方便人工查看。
HTML报告的结构用简单表格就够,把端口扫描、服务识别、安全响应头检测等各环节的结果分别放在不同的表格里,一眼就能看出问题集中在哪里。
python复制import json
def save_json_report(result, filename):
with open(filename, "w", encoding="utf-8") as fp:
json.dump(result, fp, ensure_ascii=False, indent=2)
这里引出一个从实践中得出的教训:报告写完一定要有可读性和可追溯性。每个发现都要附上对应的证据,比如“这个弱口令是用哪个用户名、哪个密码验证成功的”,而不是只说“存在弱口令”。因为没有证据的结论在后续沟通中很容易被否定。
5.3 稳定性和性能控制
工具在实验室环境里跑通了,不代表在真实环境里就稳定。我总结出三个必须处理的工程细节:
超时控制。 网络请求必须显式设置timeout,否则一个挂了的目标会让整个线程卡住。我在所有涉及网络调用的地方都加了timeout参数,这是最基础也最重要的一步。
重试机制。 网络偶尔抖动一次,可能导致本该返回“开放”的端口被误判为“关闭”。我在扫描函数里加了简单的重试逻辑:第一次超时就再试一次,只有连续两次失败才认为是关闭。这个改动让扫描结果在真实网络里的准确率提升了不少。
异常隔离。 一个模块抛异常不能拖垮整个工具。我在每个检测函数的边界都包了try/except,异常信息写入日志,主流程继续往下走。安全测试工具的一个特点是它面对的环境非常不可控——目标可能关机、防火墙可能拦截、服务可能半开,异常处理不到位,工具就会很脆弱。
6. 实战中踩过的坑和必须守住的合规红线
6.1 排查过程:扫描器“假死”与误报
第一次写完端口扫描器,我在一台虚拟机里试跑,结果发现扫描中途卡住不动,进度条长时间停在某个端口上。当时第一反应是代码死循环了,于是加了日志打印,发现卡在connect_ex一直不返回。
查了一下原因,是目标机器上有一个端口既有监听又有防火墙规则,导致TCP连接处于半开状态,connect_ex既不成功也不失败,一直等到超时。而我的代码在遇到超时异常时处理得不够干净,导致线程迟迟不释放。定位到问题之后我做了两处修改:一是把socket的超时时间缩短到0.8秒;二是给整个线程池加了as_completed的迭代方式,遍历完所有future之后再统一关闭线程池。
这个坑的本质是对不可靠网络环境的估计不足。本机回环测试时永远不会有半开连接,但跨网络环境什么情况都可能发生。所以我后来在设计所有网络脚本时都把“最坏情况”放在第一位来考虑。
6.2 Python版本和环境依赖的兼容问题
第二个常见问题是环境一致性。我在公司电脑上用Python 3.12写的代码,拿到自己的老笔记本上一跑就报语法错误或者模块缺失。
解决办法有两个:第一,项目根目录必须放requirements.txt,记录所有第三方库的版本;第二,尽量用虚拟环境运行,不要依赖系统全局Python。之前热词推荐里好多人问“Linux系统安装Python”的问题,我额外提一句:在Linux上用系统包管理器安装Python没问题,但跑项目一定要单独创建虚拟环境,因为很多发行版的系统Python是给系统工具用的,随意替换版本会影响系统稳定。
我自己的项目结构习惯是:
code复制ethack_toolkit/
├── ethack_cli.py # 入口
├── modules/
│ ├── scanner.py # 端口扫描
│ ├── banner.py # 服务识别
│ ├── weakpass.py # 弱口令检测
│ └── webcheck.py # Web检测
├── dicts/
│ └── common.txt # 字典
└── reports/
└── output.html # 报告
6.3 红线提醒:哪些事情绝对不能做
最后这部分,我想把它单独提出来,因为这是这个项目里最容易被忽略、但后果最严重的一部分。
第一,未经授权扫描就是违法。 不管你的动机是学习还是好奇,只要没有书面授权,对任何非自己所有的系统进行端口扫描、弱口令检测,都触碰法律红线。想练习就用靶场,想实战就做授权的渗透测试项目。
第二,只能在授权范围内操作。 授权写明了哪几台机器,就只碰哪几台。发现了一个不在范围内的设备存在漏洞,正确的做法是记录下来,联系客户确认是否扩容授权,而不是顺手检测一下。
第三,发现漏洞不要公开。 在真实测试项目中发现的漏洞,要遵循负责任披露的流程:先通知资产所有者,给出修复建议,等对方修复后再讨论是否公开。任何把漏洞信息发到公开平台或社交媒体上的行为,都可能造成真实损失。
第四,不要下载和使用真实攻击工具。 练习阶段自己写的、逻辑清晰的检测代码,价值远大于去GitHub上拉一个现成的攻击框架跑一下。前者让你理解原理,后者只让你变成一个“工具使用者”。
说实话,这些红线每一条背后都有真实案件作为教训。我见过有年轻的安全爱好者因为对自己学校的系统做了一次“测试”,最后被校方约谈甚至报警,前程尽毁。这个行业不缺技术天才,真正缺的是技术成熟度和职业操守同样在线的人。
回到这个项目本身,我的体会是:一次完整的“从原理到代码落地”演练,最有价值的不是那几十个检测结果,而是你被迫去思考的那些工程问题——并发怎么控制、超时怎么设置、结果怎么组织、异常怎么处理。这些思考会在你未来的任何Python项目里反复用到,比背下来一个端口号列表有用得多。
如果你照着文章把代码跑通了,我建议你再往前走一步:把端口扫描从connect方式改成SYN方式,或者给报告增加一个简单的统计图表。改代码的过程,其实就是深入理解原理的过程。这个工具我已经用了很久,每次遇到新的检测需求,我都会往modules/目录里加一个新模块,慢慢它就从一个玩具长成了真正能交付的检测工具。希望你的那份,也能这样长起来。
