Python网络安全工具集:端口扫描到日志分析的实践拆解

发布时间:2026/10/2 3:11:03
Python网络安全工具集:端口扫描到日志分析的实践拆解 简介这是一份基于Python实现的网络安全工具集源码面向渗透测试初学者与安全编程爱好者覆盖信息搜集、漏洞检测、数据加密、身份认证、流量分析、模糊测试及远程控制等常见安全场景。资源共62个文件以45个py脚本为核心辅以passwords/username字典、txt配置、xml及result结果文件zip压缩包约3.01MB模块划分清晰。已有68人学习下载。包内搭建了较完整的渗透测试框架包含POC/EXP脚本与py2exe转换示例DNS解析、子域名挖掘、邮件爬取等被动搜集模块以及基于ICMP/TCP/UDP的主机发现、端口服务识别、敏感目录探测等主动探测模块。漏洞检测覆盖Redis未授权访问、XXE注入、SQL盲注布尔/时间型与SQLMap Tamper脚本编写并附带MD5/AES/DES/Base64加密、弱口令检测、字典生成、DDoS与ARP欺骗等实用工具适合作为Python安全编程实战参考。1. 一个 Python 网络安全工具集能让你少写一半重复脚本拿到一个“基于 Python 的网络安全工具集”源码包第一反应别急着解压看文件列表先想清楚它要解决什么把安全自查里那些高频、重复、容易写错的网络操作——端口探测、子域收集、目录扫描、日志分析——收拢成一组能直接改、直接跑的 Python 脚本。它不是某个框架也不是商业平台的平替而是一套能脱离界面、在命令行里完成资产盘点的起始工具箱。适合两类人一类是刚入门的 Python 开发者想用具体项目理解 socket、requests、线程这些基础库到底能干什么另一类是负责内网或云上资产自查的运维需要快速出报告又不想每次重复造轮子。看懂它的实现比背完一套入门教程更能建立“安全测试的代码手感”这也是我把它拿出来反复拆的原因。2. 先搭好环境从 zip 压缩包到能跑的 Python 项目这类工具集拿到手是什么形态一般是 zip 包解压后一串 .py 文件、一份词表和若干文本资源。如果按默认路径直接双击运行往往用的是系统 Python很容易在版本和依赖上踩坑。我的习惯是先建虚拟环境再做一次“最小命令”冒烟测试保证代码跑得起来再谈后面的模块拆解。2.1 为什么锁 Python 3.10而不是跟系统默认不少服务器自带的是 Python 3.6、3.8跑简单 socket 脚本没问题但工具集里往往用到 f-string 调试、类型注解这类新特性低版本会在 import 阶段直接报语法错误。更重要的是第三方库的兼容线requests、dnspython 这类库的新版本已经陆续放弃对 3.7 以下的官方支持老版本库又有已知安全问题做安全工具反而引入风险。另一个常见的翻车点是 Anaconda。做数据分析用 conda 没问题但拿来跑命令行安全脚本base 环境里的包版本往往和项目要求不一致装一个包顺带升级一大堆最后连解释器都被换掉。我一般会新建虚拟环境省得把工作电脑变成“黑匣子”。创建命令很简单# 在项目目录下创建虚拟环境Python 版本用 3.10 python3.10 -m venv .venv # 激活环境macOS/Linux source .venv/bin/activate # Windows 用这个激活 # .venv\Scripts\activate # 升级 pip避免后续依赖解析出问题 python -m pip install --upgrade pip要点是venv基于当前解释器创建但如果系统里只有一个旧版 Pythonpython3.10这个名字就不存在。这时先去官网装一个 3.10 解释器再用完整路径创建环境。激活后which pythonWindows 用where python指到的必须是.venv目录下的解释器这步对了后面依赖才不乱。环境风险我的处理系统自带 Python 2语法不兼容requests 生态早已放弃直接不碰Python 3.6 / 3.8新特性缺失新版依赖装不上升级到 3.10Anaconda base包多且版本锁定容易互相污染单独建 venv不共用 base还有一点容易忽略工具集里的脚本可能依赖特定平台路径。比如日志分析模块要读/var/log/auth.logWindows 上就没有这个文件此时要么在 Linux 主机上跑要么把远端日志拉到本地离线分析。解压后先扫一眼源码里有没有写死/var/log或os.system调用能提前避免一堆环境报错。这一点对在 Windows 上用 VSCode 调试的 Python 新手尤其容易踩。2.2 requirements 清单和目录结构怎么摆解压 zip 后先找有没有requirements.txt没有就自己补。工具集的最小依赖其实很少网络请求用 requests其余大多基于标准库。一份最小依赖文件只需要一行requests2.31,3.0版本范围写成requests2.31,3.0而不是裸写requests是因为安全工具要避免不可控的大版本变更。2.x 到 3.x 在请求头默认行为上会有变化锁住大版本能保证今天跑通的结果明天还能复现。安装命令如下国内网络环境建议配镜像源否则大文件下载容易超时pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple安装完做一次冒烟测试确保 requests 没有装进“另一个环境”python -c import requests; print(requests.__version__)能打印版本号说明当前环境已经可用。常见的项目结构我会保持成下面这样词表单独放不跟脚本混在一起. ├── port_scanner.py # 端口扫描 ├── subdomain_collector.py # 子域收集 ├── dir_scan.py # 目录探测 ├── log_analyzer.py # 日志分析 ├── common.txt # 目录扫描用的词表 ├── output/ # 扫描结果统一放这里 └── requirements.txtcommon.txt是目录扫描的“方向盘”词表质量直接决定结果数量。后面讲目录扫描时会细说怎么维护它。2.3 用 VSCode 调试时的解释器选择开发这类脚本我一般用 VSCode因为单文件调试比 IDE 轻。打开项目后第一步不是写代码而是把解释器指到虚拟环境命令面板里搜 “Python: Select Interpreter”选.venv/bin/python。不指定的话VSCode 会默认用全局解释器运行结果和终端完全不一致这是新手最容易困惑的地方看起来玄学其实只是选错了解释器。配置完成后终端里python -c import requests和编辑器里的运行按钮应该共用同一套环境。还有一个常被忽略的细节VSCode 的 Python 插件会缓存解释器列表新建的 venv 有时不会立刻出现在选项里。这时可以直接在设置里把python.defaultInterpreterPath指到.venv/bin/python然后重载窗口。与其在多个虚拟环境之间反复切换不如把默认解释器固定到项目环境省心很多。搭环境这部分花十分钟是有回报的后面所有脚本的依赖问题都会在这里一次性解决。3. 拆开核心源码端口、子域、目录与日志的实现逻辑工具集里的每个脚本本质都是“构造请求 解析响应 落盘结果”。理解这个套路后四个模块其实是在不同层次做同一件事。3.1 端口扫描socket connect_ex 比 ping 更适合探活先看一段核心代码。端口扫描最基础的做法是建一个 TCP 连接能连上就说明端口在监听。connect_ex返回 0 表示成功其他值就是各种失败原因这是比connect更友好的接口因为它不会抛异常打断流程。import socket import threading from queue import Queue, Empty def scan_port(host: str, port: int, timeout: float 1.0) - bool: 对单个端口发起 TCP 连接测试返回端口是否开放。 try: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: sock.settimeout(timeout) result sock.connect_ex((host, port)) return result 0 except socket.gaierror: # 域名解析失败直接视为不可达 return False def run_scan(host: str, ports, threads: int 50, timeout: float 1.0): port_queue: Queue Queue() for p in ports: port_queue.put(p) open_ports [] lock threading.Lock() def worker(): while True: try: port port_queue.get_nowait() except Empty: break ok scan_port(host, port, timeout) if ok: with lock: open_ports.append(port) port_queue.task_done() workers [threading.Thread(targetworker) for _ in range(threads)] for t in workers: t.start() # 等待所有扫描任务处理完再回收线程 port_queue.join() for t in workers: t.join() return sorted(open_ports)逻辑说明with socket.socket(...)保证用完关闭连接避免句柄泄漏Queue用来做任务分发比手动切片公平得多端口数量不均匀时不会出现某个线程空转。get_nowait()配合Empty异常是线程安全的标准写法避免多线程在queue.empty()判断后产生竞争。线程数为 50200 时扫描 1000 个端口通常在一两分钟内完成。参数上timeout是最大的调节旋钮。内网环境 0.50.8 秒足够公网主机建议 1.52 秒不然丢包会造成大量漏报。注意这里用的是 TCP connect 扫描特征是“主动握手”容易被目标的安全设备记录做合规自查没问题但不要用它挑战任何没有授权的系统这是底线。为什么不用 ping 探活ICMP 在很多主机上默认禁回显ping 不通不代表主机不存在。更可靠的做法是“TCP 探活”挑 22、80、443 三个端口做快速连接有一个成功就认为主机在线。工具集里通常不会把这一步单独拆出来但实际使用时你完全可以在扫描前加一个is_host_alive(host)函数来过滤离线 IP能省一半时间。注意所有探测手段只建议在自有资产或获得书面授权的范围内使用授权边界不清晰时宁可少扫。3.2 子域收集本质是爬虫式采集子域收集我习惯先从证书透明度日志拉数据再拿 DNS 做二次确认。crt.sh 是一个免费的 CT 日志查询接口返回 JSON比起字典爆破域名的效率高得多属于“被动采集”适合在授权范围内做资产盘点。import socket import requests UA {User-Agent: Mozilla/5.0 (Security Audit)} def collect_from_crtsh(domain: str, timeout: int 15) - list[str]: 从证书透明度日志收集子域去除通配符和重复项。 url fhttps://crt.sh/?q%25.{domain}outputjson resp requests.get(url, headersUA, timeouttimeout) names: set[str] set() if resp.status_code ! 200: return [] for entry in resp.json(): name_value entry.get(name_value, ) for name in name_value.split(\n): name name.strip().lstrip(*.) if name.endswith(domain) and name ! domain: names.add(name.lower()) return sorted(names) def verify_with_dns(subdomains: list[str]) - list[str]: 用系统 DNS 解析确认子域是否还有效过滤已删除的记录。 alive [] for sub in subdomains: try: socket.getaddrinfo(sub, 443, socket.AF_UNSPEC) alive.append(sub) except socket.gaierror: continue return alive逻辑说明crt.sh 的q参数里%25是 URL 编码后的%表示“以任意前缀开头”相当于 SQL 里的 LIKE 查询。name_value字段可能同时挂多个域名按换行符拆开逐个清洗。lstrip(*.)把*.example.com改成example.com避免收集到通配符条目。DNS 二次确认很关键。证书日志里有大量已删除的解析记录不验证直接拿去扫会浪费时间。这里getaddrinfo指定AF_UNSPEC同时兼容 A 和 AAAA 记录不会把只有 IPv6 地址的子域误判成失效。参数上timeout建议 1015 秒crt.sh 响应时快时慢太短容易超时也不要同时向它发大量并发请求它的反爬不像商业产品那么强但请求频率过高照样返回 403这就是后面避坑章会展开的“玄学”。我在实际使用中会在两次请求之间加time.sleep(1)并把这一阶段设计成“可断点续跑”收集结果先落 CSV再交给下一步处理。理解了这段代码就能明白“python 爬虫”的思路和子域收集是相通的构造请求、解析响应、并发抓取。把这里面的请求头、Cookie 策略换成业务数据源就是一个标准爬虫。3.3 目录扫描字典驱动的状态码判断拿到 Web 服务后下一步是看服务里挂了哪些路径。目录扫描本质是“拿着字典不停地发起 HTTP 请求根据状态码判断路径是否存在”。它的效率取决于两件事并发模型和词表质量。import threading import requests from queue import Queue, Empty TIMEOUT 5 ALLOWED_CODES (200, 301, 302, 403) def probe_path(base_url: str, path: str) - tuple[str, int] | None: 探测单个路径只返回有意义的状态码。 url base_url.rstrip(/) / path.lstrip(/) try: r requests.get( url, timeoutTIMEOUT, allow_redirectsFalse, headers{User-Agent: Mozilla/5.0}, ) if r.status_code in ALLOWED_CODES: return url, r.status_code except requests.RequestException: pass return None def run_dirscan(base_url: str, wordlist: list[str], threads: int 10) - list: q: Queue Queue() results [] lock threading.Lock() for w in wordlist: q.put(w) def worker(): while True: try: path q.get_nowait() except Empty: break res probe_path(base_url, path) if res: with lock: results.append(res) q.task_done() ts [threading.Thread(targetworker, daemonTrue) for _ in range(threads)] for t in ts: t.start() q.join() for t in ts: t.join(timeout1) return results逻辑说明这里用requests.get而不是 HEAD因为不少框架对 HEAD 响应不完整会漏报。allow_redirectsFalse很重要否则 302 会被自动跟随最终拿到的状态码掩盖了真实跳转关系。403 也要记它在安全语境里往往意味着“路径存在但禁止访问”比 404 更有价值。参数上threads不建议超过 20目录扫描属于高频请求太大会触发 WAF 封禁。TIMEOUT对公网目标给 5 秒内网可以缩到 2 秒。词表方面工具集自带的common.txt通常是几千行的高频路径我会在此基础上追加业务关键词比如upload/、api/v1/、.git/、backup.zip这类。实际扫描时优先看 200 和 403301/302 需要再看跳转目标。3.4 日志分析从鉴权日志里找暴力破解痕迹相比前面三个主动探测模块日志分析是被动审计也是工具集里容易被忽视的“隐藏宝石”。对运维来说最常用的场景是把失败的 SSH 登录日志拉出来统计找出正在对服务器做口令尝试的来源 IP。import re from collections import Counter def analyze_auth_log(log_path: str, top_n: int 20) - list[tuple[str, int]]: 统计登录失败次数最高的来源 IP。 pattern re.compile( rFailed password for .* from (\d{1,3}(?:\.\d{1,3}){3}) ) counter: Counter[str] Counter() with open(log_path, r, errorsignore) as f: for line in f: m pattern.search(line) if m: counter[m.group(1)] 1 return counter.most_common(top_n)逻辑说明正则里的Failed password for .* from IP是 Linux 默认 SSH 日志的格式取自/var/log/auth.logDebian/Ubuntu或/var/log/secureCentOS。用Counter做统计比手写字典更简洁most_common(top_n)直接给出 Top 20。这里的参数是正则本身如果日志格式带 IPv6或者系统改了 sshd 的日志级别匹配不到是正常的。我的做法是先tail -5 /var/log/auth.log看真实格式再决定正则。加一个参数--ipv6支持(?:[0-9a-fA-F]{0,4}:){2,7}[0-9a-fA-F]{0,4}就能兼容双栈环境。如果日志文件超过 1GB不要用readlines()逐行for line in f是最省内存的读法。这段代码把“日志黑匣子”变成了可视化统计是自动化防守的第一步。4. 串成一次真实自查从资产发现到结果解读单看模块都很简单真正考验人的是怎么组合。这里以“给一批内网资产做授权自查”为例把工具集串成一条流水线。假设你已经照前面的方式把四个模块都改成了带argparse参数的程序下面这条命令序列可以直接跑如果源码里原本是写死参数的就把等号后面的值填到对应变量里。4.1 一条命令序列完成内网主机“体检”# 第1步端口扫描先确认常用业务端口是否暴露 python port_scanner.py --host 192.168.1.10 \ --ports 22,80,443,3306,6379,9200 \ --threads 100 --timeout 0.8 --output output/ports.json # 第2步域名资产收集确认有哪些子域对外暴露 python subdomain_collector.py --domain example.com \ --verify --output output/subdomains.json # 第3步Web 目录探测对发现的 Web 服务做路径检查 python dir_scan.py --url http://192.168.1.10 \ --wordlist common.txt --threads 10 --output output/paths.json # 第4步SSH 日志审计看这台机器最近有没有被扫 python log_analyzer.py --log /var/log/auth.log --top 20这些命令对应前面四个模块参数顺序保持一致。--output参数是很多原始工具集没有显式提供的我会建议你补上因为所有结果集中到output/后续写报告和二次分析会方便得多。逻辑说明第1步先把“哪些端口在线”摸清第2步判断“域名下有哪些系统”第3步深入 Web 层第4步被动确认“是否已经有人在对这台机器做尝试”。这个顺序是典型的资产盘点路线先宽后深避免无头苍蝇式乱扫。4.2 参数怎么定超时、线程、端口集合的选择参数内网推荐值公网推荐值说明--timeout0.5 ~ 0.8s1.5 ~ 2s内网延迟低公网需要更多重试余量--threads100 ~ 20030 ~ 50公网线程太高容易被边缘设备拦截端口集合业务端口 高危端口相同但去掉数据库公网数据库端口不该暴露发现即风险目录扫描线程10 ~ 205 ~ 10目录扫描比端口扫描更容易触发 WAF核心思想是“震荡收敛”先用低超时、大线程快速摸一遍发现有超时丢包的主机再针对它们提高超时重扫。不要一上来就全扫 65535 个端口先扫 Top 20 端口找出高价值目标再决定要不要深扫。手工做这件事容易忘工具集的意义在于把流程固定下来。参数定好后我会把它们固化到一个 JSON 配置文件里脚本启动时通过--config scan_config.json加载。这样不同场景各留一份配置内网周巡检、新业务上线前自查、云环境变更后复扫切换时不靠脑子记参数。工具集默认不一定有这个能力但源码在手加一个json.load的入口很快。4.3 结果解读哪些必须处理哪些可以忽略端口扫描返回开放端口后先别急着激动。看到 6379说明 Redis 可能无认证暴露看到 9200说明 Elasticsearch 可能裸奔看到 3306数据库端口映射到公网本身就值得警惕。这些是“必须处理”。而像 443、22 这类要看是否承载业务不能一刀切关闭。目录扫描的返回要重点看 200 和 403200 代表可访问403 代表存在但被权限控制后者在安全上同样是信号。302 则要结合Location字段判断是不是“跳转到登录页”如果是说明该路径有鉴权风险相对小。子域收集里“存活但无 HTTP 服务”的子域常见于邮件或内部系统优先级排在对外开放 Web 服务后面。日志分析的结果最直接一个 IP 在短时间内失败上百次基本可以判定是在持续尝试登录。处理方式不是把 IP 永久封掉而是先确认它是不是你自己的跳板机再考虑在防火墙加临时规则。这一步依赖人的判断脚本给的是证据链不是最终结论。每次自查结束把结果里的开放端口、403 路径、可疑子域分别归档并记一句“当时为什么处理或不处理”。几个月后再看同一批资产能直接看出变化新端口是不是业务迭代导致的旧端口是不是忘了关。这一层历史对比能力很多商业工具要额外付费模块而源码包里一份 JSON 历史就能实现。5. 避坑跑这套源码最容易翻车的五个地方下面每一条都是我自己实践里踩过、或帮人排过的高频问题。按“现象 → 原因 → 解决”写方便直接对照排错。如果一次遇到好几个多半是环境问题而不是代码逻辑问题先回第 2 章重新确认解释器。5.1 报 requests 导入失败但明明刚装过现象终端里python进交互模式后import requests正常一跑项目脚本就报ModuleNotFoundError。原因脚本被 IDE 或双击启动后用的是另一个解释器不在同一个虚拟环境里。更隐蔽的是项目目录下存在一个叫requests.py的残留文件Python 会优先导入同名本地文件把这个真正的库“遮蔽”掉。解决先which python确认解释器位置再看pip list里有没有 requests。全程在终端里激活 venv 后运行不双击.py文件同时检查项目根目录有没有requests.py、socket.py这类同名文件删掉即可。5.2 端口扫描结果全是 filtered和云监控对不上现象用脚本扫出来的端口全是 closed/filtered但同一台机器在云控制台或另一套扫描工具里能看到端口开放。原因云安全组或服务器防火墙在丢弃 SYN 包也可能目标主机的入侵防御设备对高频 TCP 握手做了限制导致connect_ex拿不到正确回包。解决先确认当前机器 IP 在目标安全组白名单里把 timeout 从 0.8 调到 2 看是否改善。再用系统自带的工具单独测一个端口交叉验证nc -zv -w 2 192.168.1.10 443这条命令会做一次完整的 TCP 握手。能通说明网络层没问题问题在脚本参数不通说明安全组确实在拦和脚本无关。5.3 crt.sh 接口 403 或超时现象子域收集脚本跑了几次后突然返回空列表手动打开 crt.sh 网页却正常。原因请求频率过高或 UA 被识别为脚本接口临时限流。crt.sh 背后的 CT 日志查询是公共资源没有商业 SLA高峰期响应慢是常态。解决给请求加固定time.sleep(1)带上浏览器 UA把超时从 5 秒提到 15 秒。再做一次带退避的重试代码层面最省事import time for attempt in range(3): try: resp requests.get(url, headersUA, timeout15) break except requests.RequestException: time.sleep(2 * attempt)再有规律地失败就换备用的证书透明度查询源或本地证书数据库不要在同一个接口上死磕。5.4 目录扫描全是 403连静态页都被挡现象dir_scan.py跑完输出一堆 403手动用浏览器访问同一路径却正常。原因脚本的请求特征太明显被 WAF 或反向代理拦截。常见触发点是无 UA、请求头顺序异常、每秒请求次数过高。解决先手动curl -I看服务端返回在脚本里带上完整的浏览器 UA 和 Accept 头把目录扫描线程降到 5并在每个请求之间加一点随机延时。这类问题最像玄学但其实都是请求特征问题按“模拟浏览器”的思路排查即可。5.5 日志分析模块匹配不到任何记录现象log_analyzer.py跑完输出空列表但登录失败明明在发生。原因日志路径不对、rsyslog 改了格式、或系统用 journald 导致/var/log/auth.log根本不存在。解决先确认文件存在并查看真实格式ls -l /var/log/auth.log tail -20 /var/log/auth.log看到真实内容后再对照正则调整。Debian 系和 RHEL 系路径不同用 systemd 的机器可以直接改用 journalctl 输出journalctl _COMMsshd --since 24 hours ago | grep Failed password把这段输出重定向成文本再喂给日志分析模块就能跳过路径问题。6. 进阶把工具集改成自己趁手的命令行武器库到这里基本用法已经跑通。更进阶的做法是把零散脚本改造成具备统一入口的小工具集让结果能进入更大的自动化闭环。6.1 让所有结果输出 JSON而不是 printprint 只适合人肉看不适合继续处理。我给每个模块加了--output参数统一输出结构{ task: port_scan, timestamp: 2025-01-01T10:00:00, target: 192.168.1.10, results: [{port: 80, state: open}] }这样后续无论是接入 Elasticsearch、生成报告还是交给大模型做初步研判都只需要一段 JSON 解析代码。工具集的真正价值从第一天起就不是“跑一次看结果”而是“结果能复用”。6.2 用 asyncio 重写端口扫描threading 能跑但连接数一大线程切换开销就明显。把端口扫描改成 asyncio 是性价比最高的改动import asyncio async def check_port(host: str, port: int, timeout: float 1.0) - bool: loop asyncio.get_running_loop() try: _, writer await asyncio.wait_for( asyncio.open_connection(host, port), timeouttimeout ) writer.close() await writer.wait_closed() return True except (asyncio.TimeoutError, OSError): return False async def run_sweep(host: str, ports: list[int]) - list[int]: jobs [check_port(host, p) for p in ports] results await asyncio.gather(*jobs) return [p for p, ok in zip(ports, results) if ok]改动后单机并发可以到上千连接比线程版快一个量级。关键点是asyncio.wait_for控制超时open_connection内部完成 TCP 握手逻辑和 socket 一致。有了这版线程模型那版就可以退役了。6.3 验证习惯每动一处先打本机改完代码别急着扫外网目标。我的习惯是先在本地起一个测试服务python -m http.server 8000然后立刻用脚本扫127.0.0.1:8000确认端口状态能被正确识别再测一个不存在的端口确认不会误报。这种“先打本机”的验证方式是我当年吃过大亏后养成的习惯——有一次把字典文件路径写错跑了整个下午所有结果都是 404回头一看是路径没加载对。工具集是拿来用的不是拿来供奉的黑匣子每一次改动都让行为更可预期这就是它作为“源码包”比闭源软件更好的原因。希望帮到你。本文还有配套的精品资源点击获取