简介这套源码包以Python语言编写是一套覆盖渗透测试常见环节的网络安全工具集适合安全测试新手、运维人员及对安全编程感兴趣的开发者。资源共62个文件以45个Python脚本为核心另有密码字典、用户名字典、结果记录、配置说明等辅助材料压缩包仅3.01MB按模块目录分类组织检索方便。目前已有68人浏览学习。工具集实现包括被动与主动信息搜集域名解析、子域挖掘、主机存活、端口服务、目录探测多种漏洞检测与利用未授权访问、外部实体注入、盲注弱口令爆破与字典生成常见加解密算法MD5、AES、DES、Base64以及流量嗅探、地址欺骗、拒绝服务、免杀技术等脚本。这些脚本既能在授权测试中直接参考也可作为学习Python安全编程的完整范例帮助理解攻击原理与防御思路。1. 一套能改能跑的Python网络安全工具集不装牛刀也能做基线检查凌晨三点收到告警一台服务器的 sshd 登录失败记录短时间暴涨。手头没有商业扫描器又不敢在核心网段随便跑重量级工具我翻开这套基于Python的网络安全工具集源码用其中不到两百行的脚本三分钟内把问题主机、开放端口和新增登录尝试刷了一遍天亮前给出了初步结论。这套工具集解决的问题很具体当你需要的是可控、可解释、能改动的检查手段而不是黑匣子式的重型扫描器时Python 的几个常规模块就够了。它适合三类人正在走网络安全学习路线、需要源码参考的入门者要做主机和日志基线检查但不想背一堆工具的运维以及想把现成代码改成自己习惯的工程师。整包不是一堆不可维护的文件而是能直接跑通的最小命令集合。2. 主机发现与端口扫描ARP探测与TCP全连接怎么选先立一条使用边界下面所有扫描和探测逻辑都只对你自己负责的服务器或已拿到书面授权的网段使用。这是做基线检查的基本前提不是写在文档里的套话而是踩过坑之后的底线。工具集里的主机发现分成两层同一广播域内用 ARP跨网段用 TCP 全连接。这个分工不是拍脑袋定的跟网络原理直接相关。2.1 为什么要自己写端口扫描而不是直接调nmapnmap 是行业事实标准但这份工具集的目的不是替代它。自己写这段代码换来三件事第一扫描过程被拆成看得懂的步骤学原理和排错都更方便第二输出格式完全可控可以直接接进告警流程和日报脚本第三只依赖 Python 和 scapy装了 Python 的机器就能跑不需要额外审批二进制工具。在很多受管环境里安装 nmap 本身就是一道流程而 Python 环境几乎每个开发机都有。另一个现实原因是权限模型不一样。nmap 的 SYN 半开扫描需要 raw socket 权限普通用户跑不起来TCP 全连接走的是操作系统正常 connect 调用普通用户就能执行。基线检查场景并不追求隐蔽性只需要确认端口开没开所以工具集默认采用 TCP connect 方式省掉一堆权限纠缠。2.2 arp_sweep.py二层发现适合内网盘点ARP 请求是广播帧目标主机会回一个单播应答所以同一广播域内的存活主机能在几百毫秒内全部浮出来。它的速度快到不需要等 TCP 超时这是二层探测最大的优势。局限也很明显只能在同一个 VLAN 里用跨网段就完全失效。# arp_sweep.py import scapy.all as sp def arp_sweep(ip_prefix: str, timeout: int 2) - list: # 构造ARP请求包询问整个 /24 网段 req sp.ARP(pdstip_prefix .0/24) # 封装成以太网广播帧发给二层所有主机 eth sp.Ether(dstff:ff:ff:ff:ff:ff) ans, _ sp.srp(eth / req, timeouttimeout, verboseFalse) hosts [] for sent, recv in ans: hosts.append({ip: recv.psrc, mac: recv.hwsrc}) return hosts这段代码的逻辑并不复杂先构造一个目标 IP 是整个 C 段的 ARP 请求包再包上以太网广播帧头然后通过 srp 函数在二层发送并等待应答。收到的每个应答包里psrc 是对端 IPhwsrc 是对端 MAC拼成字典列表就是你要的存活主机清单。参数上最值得调的是 timeout。内网正常情况下 1 到 2 秒足够如果网段里有大量离线主机或者网络设备响应慢可以抬到 3 秒。verbose 建议始终设为 False否则每收一个包刷一行输出看结果时反而干扰。ip_prefix 传 192.168.1 这种前三段即可函数内部会自动拼 0/24这个设计是为了防止你手滑传成带网段的完整地址。2.3 tcp_connect_scan.py三层探测适合跨网段确认端口ARP 只能告诉你主机在不在想知道端口状态必须走 TCP。TCP 全连接扫描的原理就是操作系统帮你去和目标端口建立完整的三次握手握手成功说明端口开放失败则关闭或不可达。因为用的是普通 socket不需要 root这也是它比 SYN 扫描更适合基线检查的原因。# tcp_connect_scan.py import socket import concurrent.futures def tcp_connect(host: str, port: int, timeout: float 1.0) - bool: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.settimeout(timeout) try: s.connect((host, port)) return True except (socket.timeout, ConnectionRefusedError): return False def scan(host: str, ports: list, timeout: float 1.0, workers: int 100) - list: results [] with concurrent.futures.ThreadPoolExecutor(max_workersworkers) as pool: future_map { pool.submit(tcp_connect, host, p, timeout): p for p in ports } for fut in concurrent.futures.as_completed(future_map): if fut.result(): results.append(future_map[fut]) return sorted(results)核心逻辑是 thread pool 里的每个 worker 对一个端口独立执行 connect成功就记录端口号。as_completed 保证先完成的先处理不会因为某个端口超时卡住整个任务。注意代码里用了上下文管理器 withsocket 用完会自动关闭避免文件描述符泄漏。参数经验值内网机器 timeout 设 1.0 秒就够了跨公网扫自己的主机建议抬到 2 到 3 秒否则延迟一高全是假阴性。workers 默认 100 是我比较常用的折中值线程太多在低配机器上反而会因为调度开销变慢而且容易被目标侧的安全设备识别成扫描特征。端口列表用逗号分隔的小批量方式比一次性扫全端口更适合日常巡检。2.4 结果归档别把扫描结果记在命令行里真正干活的时候扫描结果是要拿去和上一次对比、写进日报的。所以工具集里每个脚本都带一个输出文件参数统一存成 JSON而不是直接 print 完就结束。import json with open(scan_result.json, w, encodingutf-8) as fp: json.dump({host: host, open_ports: ports}, fp, ensure_asciiFalse, indent2)常见做法是直接调用脚本时追加 -o 参数指定输出路径。命令行执行方式是这样python arp_sweep.py 192.168.1.0/24 --timeout 2 -o hosts.json python tcp_connect_scan.py 192.168.1.10 -p 22,80,443,3306 -w 100 -t 1.0第一条命令扫 192.168.1.0/24 的存活主机第二条对指定 IP 的四五个常用端口做确认。如果 ARP 探测返回空先别怀疑脚本检查自己这台机器是不是和目标不在同一 VLAN如果 TCP 全连接全 closed先看目标机器上的防火墙和云安全组再回来看代码。2.5 两个模块配合的完整流程实际巡检时我一般会先跑 ARP 扫出存活清单再拿清单里的 IP 去过 TCP 端口而不是对着整个网段做全量 TCP 扫描。这样做一是省时间二是减少无关告警。用一个简单的 bash 循环就能把两步串起来但要注意千万不能把扫描目标搞错每次跑之前都要核对 IP 段是不是你负责的资产。3. 日志与流量分析把登录审计和pcap统计做成两个能用的脚本主机发现的结论只能告诉你端口是开的想知道这段时间发生了什么必须看日志和流量。这是工具集里最贴近日常运维的部分也是我实际使用频率最高的两个脚本。它们的定位是离线分析不抓取实时流量不劫持任何连接只处理你手上已有的日志文件和 pcap 包。3.1 基线检查里最常遇见的三个日志场景场景主要日志来源重点观察字段登录失败聚集/var/log/auth.log 或 /var/log/secure源 IP、用户名、失败次数端口异常连接应用访问日志同一源 IP 的访问频率流量包大小异常抓包文件 pcap单流字节数、包数量三个场景对应三种不同的分析思路。登录失败聚集是安全告警里最常见的一种需要按用户名和源 IP 做聚合排序端口异常连接看的是短时间内是否出现大量不同目的端口的请求流量包大小异常则要回放 pcap 文件统计哪些会话占用了最多字节。工具集分别覆盖了第一和第三个场景第二个场景的字段高度依赖具体应用源码里留了扩展的位置。3.2 ssh_log_parser.py登录失败聚合脚本解析 auth.log 最怕的是日志格式不统一不同发行版的时间戳格式、sshd 的措辞都有差异。下面这个实现针对的是最主流的 rsyslog 时间戳格式也是 Debian 系和 RedHat 系都能见到的写法。# ssh_log_parser.py from collections import Counter import re pattern re.compile( r(\d{4}-\d{2}-\d{2}T?\d{2}:\d{2}:\d{2}).*?sshd.*? r(Failed|Accepted).*?for\s(\w).*?from\s([\d.]) ) def parse_log(path: str) - dict: failed Counter() accepted Counter() with open(path, r, encodingutf-8, errorsignore) as fp: for line in fp: m pattern.search(line) if not m: continue time_str, action, user, src_ip m.groups() if action Failed: failed[(user, src_ip)] 1 elif action Accepted: accepted[(user, src_ip)] 1 return { failed: failed.most_common(20), accepted: accepted.most_common(20), }逐行扫描文件用正则把时间、动作、用户名、源 IP 四个字段抠出来然后放进 Counter 聚合。正则里的 T? 是为了兼容 rsyslog 时间戳和 ISO 格式时间戳的差异for 和 from 是 sshd 日志里固定出现的连接词。errorsignore 不是偷懒而是很多老系统的日志里混着乱码控制符不忽略编码错误整个解析就会中断。如果碰到Jan 1 12:00:00 host sshd[...]这种老式 BSD 风格时间戳把正则第一段改成\w{3}\s\d{1,2} [\d:]{8}就能适配。解析完成后结果会按失败次数倒序输出前 20 条重点关注哪些 IP 同时尝试了多个用户名这通常比单个用户被反复尝试更值得警惕。3.3 pcap_stats.py用scapy离线统计流量会话抓包文件是网络问题排查时的黑匣子但很多人拿到 pcap 只会用 Wireshark 打开手动翻。工具集里的 pcap 统计脚本做的是另一件事把包里 IP 会话的字节数自动聚合出来直接列出流量最大的前 N 条流。# pcap_stats.py from scapy.all import rdpcap, IP def summarize(pcap_path: str, top_n: int 10) - dict: pkts rdpcap(pcap_path) flow_bytes {} for p in pkts: if IP in p: key (p[IP].src, p[IP].dst) flow_bytes[key] flow_bytes.get(key, 0) p[IP].len top sorted(flow_bytes.items(), keylambda kv: kv[1], reverseTrue)[:top_n] return { total_packets: len(pkts), top_flows: [ {src: k[0], dst: k[1], bytes: v} for k, v in top ], }逻辑很直白遍历每个包只要带了 IP 层就以源地址和目标地址为键累加 IP 包总长度最后按字节数排序取前 N 条。rdpcap 适合小文件离线分析文件超过一两百兆时内存会吃紧换成 scapy 的 PcapReader 做流式读取更稳。注意一个细节这里统计用的是 p[IP].len也就是 IP 包总长度不含以太网帧头。如果你想算的是链路层实际的流量要在此基础上加 14 字节的以太网头或者直接按 len(p) 统计。这个差异在短包多的场景下会造成明显偏差。注意pcap 分析只针对授权环境保存的抓包文件。不要在别人没有授权你的网络上做抓包和流量分析。3.4 把分析结果变成可读的文本报告日志和 pcap 脚本的返回值都是字典结构可以直接 dump 成 JSON但实际巡检时我更喜欢先看一个对齐的文本表问题大致定位后再去看详细 JSON。一个简单的输出函数就能满足需求def print_top_failed(failed: Counter) - None: print(f{usersrc_ip:28} {count:6}) print(- * 36) for (user, src_ip), count in failed: print(f{user src_ip:28} {count:6}){:28} 是左对齐并占 28 个字符宽度{:6} 是右对齐占 6 个宽度不用额外引入 tabulate 库输出也已经足够整齐。实际使用时我会把这份文本报告直接追加到当天的巡检记录文件里作为原始依据保留。4. 信息收集与风险暴露面排查域名、CVE与调度入口排查完主机和日志之后还有一类问题是运维和安全管理经常会接到的这个域名指向哪里、注册信息是否正常、某个服务版本有没有已知漏洞风险。工具集里的信息收集模块就干这三件事全部限定在“自己负责的资产”范围内使用。它的价值不是帮你去探测别人而是快速把自家资产整理成一张可核对的风险清单。4.1 域名解析与Whois查询先认清楚资产归属做暴露面排查的第一步是搞清楚域名解析到哪些 IP注册人信息是否还在自己名下。很多历史遗留域名挂着老员工的个人邮箱注册商、NS 记录也是错的这类问题不查 Whois 根本发现不了。# dns_lookup.py import dns.resolver import whois def query_dns(domain: str, record_type: str A) - list: answers dns.resolver.resolve(domain, record_type) return [str(r) for r in answers] def query_whois(domain: str) - dict: w whois.whois(domain) return { registrar: w.registrar, creation_date: str(w.creation_date), expiration_date: str(w.expiration_date), name_servers: w.name_servers, }依赖只有 dnspython 和 python-whois 两个库pip 安装即可。query_dns 默认查 A 记录需要查 MX 或 AAAA 时把第二个参数换掉就行。query_whois 只取了四个字段注册商、注册时间、到期时间、NS 记录。为什么只取这四个因为日常排查里续费提醒靠到期时间域名归属判断靠注册商和 NS 记录注册时间用来识别那些转手多次的老域名。挖坑提醒whois.whois 返回的 creation_date 可能是列表也可能是单个对象不同顶级域格式不统一直接拿去比较日期前要先判断类型。遇到过不少脚本在这里翻车最后处理方式很简单取列表第一个元素再转字符串。4.2 CVE风险查询把服务版本变成更新时间表日志和端口结果里经常会冒出一个服务版本号比如 report 里写着 OpenSSH 8.2p1。要不要立项升级答案藏在公开漏洞库里。这个模块的作用就是拿着服务名和版本号去查已知漏洞编号把“疑似有问题”变成“有具体 CVE 编号”。# cve_query.py import requests def query_cve(service: str, version: str) - list: keyword f{service} {version} url https://nvd.nist.gov/rest/api/cves/2.0 params {keywordSearch: keyword, resultsPerPage: 5} resp requests.get(url, paramsparams, timeout10) resp.raise_for_status() out [] for item in resp.json().get(vulnerabilities, []): cve item[cve] out.append({ id: cve[id], published: cve.get(published, ), summary: cve[descriptions][0][value][:80], }) return out接口用的 NVD 公开 API不需要鉴权但每次查询的 resultsPerPage 我习惯控制在 5 条以内避免响应体过大。拼 keyword 的时候有个经验直接传“OpenSSH 8.2p1”这种带小版本号的字符串往往查不齐改成“OpenSSH 8.2”命中率高很多因为漏洞库描述里通常只写到小版本段。timeout 设 10 秒接口响应慢时宁可失败重试也不要无限等下去。注意这个模块只负责查询风险信息描述字段是英文原文写告警邮件时要转成自己的话不要直接贴一段英文给业务方看。4.3 统一入口recon.py别让脚本散落一地有了 DNS、Whois、CVE 三个函数之后自然需要一个聚合入口否则每次排查都要写三行 Python 调三个文件越用越难受。工具集里加了一个 recon.py 做调度参数解析。# recon.py import argparse import json def main(): p argparse.ArgumentParser(descriptionasset recon for self-owned targets) p.add_argument(--domain, requiredTrue, helpdomain to inspect) p.add_argument(--cve, nargs, helpservice/version pairs: redis 5.0.7) args p.parse_args() out {domain: args.domain} if args.cve: out[cves] [] for svc, ver in zip(args.cve[::2], args.cve[1::2]): out[cves].extend(query_cve(svc, ver)) with open(recon_report.json, w, encodingutf-8) as fp: json.dump(out, fp, ensure_asciiFalse, indent2) if __name__ __main__: main()命令行调用方式是这样python recon.py --domain example.com --cve redis 5.0.7 nginx 1.18.0zip(args.cve[::2], args.cve[1::2]) 是把成对参数拆开的常见写法偶数下标是服务名奇数下标是版本号可以一次传多组。nargs 比定义两个参数灵活加几个服务就传几组不用改代码。输出的 recon_report.json 会包含域名和所有查询到的 CVE 列表直接作为资产风险报告的附件存档。5. 避坑与常见问题环境、编码、权限与误报排查工具集里的脚本加起来不到一千行但真正让它跑起来并持续稳定输出报告靠的是下面这些坑的排查经验。这些问题我在不同的机器上都遇到过每一条都对应过一次真实的翻车现场。5.1 scapy在Linux上提示Operation not permitted现象arp_sweep 跑起来直接抛异常提示创建 socket 时 Operation not permitted。原因ARP 请求属于 raw socket 操作需要 root 权限或者 CAP_NET_RAW 能力。普通用户跑不了容器环境默认也没这个能力。解决本机执行用 sudo 提权容器场景启动时加--cap-addNET_RAW如果只是要端口存活信息改成 TCP 全连接扫描那个不需要特权。我在生产环境里更倾向后者尽量不给脚本提权。5.2 Windows上scapy能导入但收不到ARP应答现象同一份 arp_sweep.py 在 Windows 上跑返回的 hosts 列表为空代码和网络都没问题。原因scapy 在 Windows 上不是靠自研的 raw socket 发包而是依赖 Npcap 或 WinPcap 的驱动接口。只装了普通网卡驱动scapy 的发送函数会静默失败不报错但也不收包。解决安装 Npcap安装时勾选“WinPcap API 兼容模式”装完重启终端再跑。这个兼容模式是很多扫描类工具能识别设备的前提老版本的 WinPcap 已经不建议使用。5.3 日志文件编码导致UnicodeDecodeError现象ssh_log_parser 解析到第几百行时抛 UnicodeDecodeError前面解析好的结果也丢了。原因老服务器的 auth.log 里混着历史遗留的 GBK 字节或者 ssh 会话日志里塞了终端控制符。以 UTF-8 严格模式打开就会中断。解决open 时加errorsignore再在正则之前过滤掉 ANSI 转义序列。常见做法是先做一次字符清洗把形如\x1b[31m的控制序列直接删掉再进解析否则你会发现用户名里混进来一些奇怪的可见字符。5.4 JSON输出里中文全变成\uXXXX现象报告文件打开以后中文内容全是\u5bc6\u7801这种转义序列没法直接阅读。原因json.dump 默认参数 ensure_asciiTrue会把所有非 ASCII 字符转成 Unicode 转义序列。这在存储侧没问题但人读起来很痛苦。解决写入时显式指定ensure_asciiFalse同时保证文件以utf-8编码打开。注意读取端也要用同样参数否则读取方拿默认编码解码很可能还是乱码。5.5 端口扫描漏报已知开放的端口扫不出来现象tcp_connect_scan 跑完遗漏了目标机器上明确监听着的端口而在目标机器上执行 ss -ltn 能看到端口在 LISTEN。原因两种情况占大多数。一是目标防火墙或云安全组对非预期来源的探测包直接丢弃超时被记成关闭二是本机 workers 开得太大文件描述符耗尽后半段任务实际没有正常执行。解决先降低并发数到 50 以内跑一次排除 fd 耗尽问题再调大 timeout 到 2 到 3 秒排除丢包导致的假阴性。如果还不命中在目标机器上用ss -ltn对比监听端口确认是不是安全组问题。做基线检查时宁可慢一点拿准确结果也不要图快拿一份不可信的清单。6. 把工具集接进日常巡检一条命令完成基线报告工具集拿到手之后第一个要做的不是去翻源码而是用一条最小命令验证整条链路能跑通。我会写一个 daily_scan.py把主机发现、端口确认和日志解析三个模块串起来输出一份带时间戳的巡检报告。# daily_scan.py import subprocess import json import datetime def run(): now datetime.datetime.now().isoformat(timespecseconds) hosts json.loads(subprocess.check_output( [python, arp_sweep.py, 192.168.1.0/24, --timeout, 2] )) result { time: now, host_count: len(hosts), hosts: hosts, assets: [] } for h in hosts: ports json.loads(subprocess.check_output( [python, tcp_connect_scan.py, h[ip], -p, 22,80,443, -t, 1.0, -w, 50] )) result[assets].append({ip: h[ip], open_ports: ports}) with open(daily_report.json, w, encodingutf-8) as fp: json.dump(result, fp, ensure_asciiFalse, indent2) if __name__ __main__: run()这份报告的好处是每次都能对比异动昨天 22 端口还开着今天清单里消失了马上能发现问题昨天 80 端口上的服务没变化今天多出来的端口可能是业务变更也可能是需要确认的异常。验证步骤很简单先在自己的一台测试机上跑一遍检查输出 JSON 里主机数和端口结果是否与 ss 命令吻合再扩大到实际巡检网段。从那以后我每次拿到新的工具集源码都会先做一件事把所有入口函数的参数捋一遍用一条最小命令验证它能出结果然后才去读里面的核心逻辑。能跑通的最小闭环比什么都重要希望帮到你。本文还有配套的精品资源点击获取