从零构建GitHub漏洞监控脚本:Python自动化安全情报获取
1. 项目概述为什么我们需要一个GitHub漏洞监控脚本在开源软件供应链安全日益受到重视的今天GitHub早已不只是代码托管平台更是安全情报的“金矿”。每天全球的安全研究员、白帽子甚至攻击者都会在GitHub上公开或无意间泄露最新的漏洞利用代码PoC/Exp、安全公告、内部配置信息乃至泄露的凭证。对于安全从业者、运维工程师和开发者而言谁能第一时间获取这些信息谁就能在攻防对抗中抢占先机或在漏洞被大规模利用前完成修补。然而手动刷新GitHub搜索页面既不现实也低效。这就是自动化监控脚本的价值所在它像一个不知疲倦的哨兵7x24小时帮你盯着GitHub上与你关心的技术栈、产品、服务相关的安全动态。一旦有新的漏洞信息出现它能立刻通过你习惯的渠道比如微信推送到你手上让你从“被动响应”转变为“主动预警”。这个项目就是教你从零开始搭建一套属于自己的、高度定制化的GitHub安全监控与实时告警系统。我们将使用Python作为核心语言因为它拥有丰富的库和简洁的语法非常适合这类自动化任务。整个流程会涵盖监控逻辑设计、GitHub API的深度使用、数据过滤清洗以及最终通过Server酱将告警推送到微信。无论你是想监控Log4j、Spring Framework这类通用组件的漏洞还是公司内部自研产品的关键词这套方案都能灵活适配。2. 核心思路与架构设计如何高效、精准地“挖矿”一个健壮的监控脚本其核心在于平衡“全面性”与“精准度”并确保稳定、低打扰的运行。盲目地抓取所有内容只会带来信息噪音而过滤条件过于苛刻又可能漏报。我们的设计思路遵循以下原则2.1 监控目标定义从关键词到搜索语法监控的起点是明确“监控什么”。GitHub提供了强大的搜索语法我们可以将其转化为监控策略漏洞利用代码Exploit/PoC通常包含exploit、poc、cve-2023-、vulnerability等词汇。可以组合搜索例如exploit poc spring security。安全公告与报告搜索advisory、security bulletin、disclosure加上产品名如advisory apache tomcat。敏感信息泄露针对自身项目监控可能误提交的密码、密钥、配置文件等如password filename:config.json或api_key filename:.env。特定CVE编号直接跟踪某个特定漏洞的讨论和代码如CVE-2023-12345。实操心得关键词需要不断迭代和调优。初期可以设置得宽泛一些运行几天后分析抓取结果将大量无关的误报关键词加入排除列表使用-keyword语法逐步提升信噪比。2.2 系统架构设计整个系统可以划分为四个核心模块形成一个清晰的数据流管道调度与触发模块负责定期如每10分钟执行监控任务。我们可以使用操作系统的定时任务如Linux的cron或Windows的Task Scheduler来触发Python脚本。数据获取与过滤模块这是脚本的核心。通过GitHub Search API使用定义好的搜索语法获取最新结果。然后对结果进行初步过滤例如排除Fork的仓库、只看最近1小时的内容、按星标数或更新时间排序等。内容解析与去重模块从API返回的JSON数据中提取出我们需要告警的关键信息仓库名、提交者、时间、文件链接、代码片段预览等。同时必须实现一个去重机制避免同一内容反复推送。通常将已推送内容的唯一标识如仓库全名提交ID记录在一个简单的文本文件或轻量级数据库如SQLite中。通知推送模块将过滤和解析后的告警信息格式化为易读的消息Markdown格式为佳通过通知渠道发送出去。这里我们选择集成Server酱的微信通知服务因为它免费、稳定且配置简单。2.3 技术选型考量语言选择Python除了库丰富其requests库处理HTTP请求、json库解析数据都非常方便。用于定时任务时也易于部署。GitHub API选择Search API这是最合适的接口。需要注意其速率限制未认证用户每分钟60次认证后大幅提升。我们将使用认证请求以获取更高的配额。存储选择本地文件对于去重记录这种简单需求使用一个JSON文件或SQLite数据库足矣避免引入外部依赖。通知选择Server酱它提供了稳定的HTTP API只需一个SCKEY就能将消息推送到绑定的微信非常适合个人或小团队使用。3. 环境准备与核心依赖配置在开始编写代码前我们需要准备好“战场”。这个过程包括安装必要的软件、配置开发环境以及获取关键的API访问凭证。3.1 Python环境与依赖库安装首先确保你的系统安装了Python 3.6或更高版本。可以通过命令行输入python --version或python3 --version来检查。接下来安装本项目所需的Python库。我们将使用pip包管理器。建议创建一个虚拟环境以隔离项目依赖但为了简化这里直接进行全局安装。打开终端或命令提示符执行以下命令pip install requests schedulerequests用于向GitHub API和Server酱API发送HTTP请求。schedule一个轻量级库用于在脚本内部实现定时循环执行方便调试。在生产环境我们更推荐使用系统级的cron或systemd timer。3.2 获取GitHub Personal Access Token为了以认证身份调用GitHub API提升速率限制并访问一些私有资源如果你需要必须创建一个Personal Access Token。登录你的GitHub账号点击右上角头像 -Settings。在左侧边栏最底部找到Developer settings。点击Personal access tokens-Tokens (classic)然后点击Generate new token (classic)。为token添加一个描述性名称例如GitHub Monitor Bot。选择权限Scopes对于搜索监控至少需要勾选repo访问仓库信息和read:user读取用户信息。如果不需要访问私有库public_repo权限也足够。为了安全遵循最小权限原则。点击Generate token。重要生成的token只会显示一次请立即将其复制并保存到安全的地方如本地的密码管理器或一个加密的配置文件中。它看起来像一串乱码ghp_xxxxxxxxxxxxxxxxxxxx。3.3 注册并配置Server酱Server酱是一个将服务器消息推送到微信的工具。访问其官网此处不提供具体网址可自行搜索“Server酱”。使用GitHub账号登录。登录后进入“发送消息”页面你会看到一个SCKEY或在新版中称为SendKey。同样复制并保存好这个Key。它是你接收消息的通道标识。使用微信扫描页面提供的二维码即可绑定你的微信。绑定后所有向这个SCKEY发送的消息都会推送到你的微信。3.4 项目目录结构规划创建一个清晰的项目文件夹有助于管理代码和配置github_monitor/ ├── config.py # 配置文件存放Token、SCKEY等敏感信息 ├── monitor.py # 主监控脚本 ├── sent_history.db # SQLite数据库文件用于记录已发送消息运行后生成 ├── requirements.txt # 项目依赖列表 └── logs/ # 日志目录可选将你的GitHub Token和Server酱的SCKEY写入config.py文件中切记不要将此文件提交到任何公开的Git仓库# config.py GITHUB_TOKEN 你的GitHub Token以ghp_开头 SERVERCHAN_SCKEY 你的Server酱 SCKEY # 可以在这里定义其他配置如搜索关键词、检查间隔等 SEARCH_KEYWORDS CVE-2023- exploit poc CHECK_INTERVAL_MINUTES 104. 监控脚本核心代码实现与解析现在我们进入核心环节一步步构建monitor.py。4.1 初始化与配置加载首先导入必要的库并加载配置。import requests import json import time import sqlite3 import logging from datetime import datetime, timedelta import schedule from config import GITHUB_TOKEN, SERVERCHAN_SCKEY, SEARCH_KEYWORDS, CHECK_INTERVAL_MINUTES # 配置日志方便调试和查看运行状态 logging.basicConfig(levellogging.INFO, format%(asctime)s - %(levelname)s - %(message)s) logger logging.getLogger(__name__) # GitHub API 端点 GITHUB_SEARCH_URL https://api.github.com/search/repositories # Server酱 API 端点 SERVERCHAN_URL fhttps://sctapi.ftqq.com/{SERVERCHAN_SCKEY}.send # 请求头加入认证信息 HEADERS { Authorization: ftoken {GITHUB_TOKEN}, Accept: application/vnd.github.v3json }这里我们使用sqlite3来创建和管理一个简单的数据库用于记录已经推送过的仓库实现去重。4.2 构建精准的GitHub搜索查询GitHub Search API功能强大我们需要构造一个高效的查询字符串q参数。查询语法遵循特定规则。def build_search_query(keywordsSEARCH_KEYWORDS): 构建GitHub搜索查询字符串。 添加常用过滤条件以提高结果相关性。 # 基础关键词 query keywords # 只搜索最近24小时内创建的仓库避免海量历史数据 # 使用ISO 8601格式的时间戳 since_time (datetime.now() - timedelta(hours24)).isoformat() query f created:{since_time} # 按星标数排序热门项目可能更值得关注 # query sort:stars # 或者按更新时间排序获取最新动态 query sort:updated # 可以排除某些语言或特定仓库 # query language:python # 只监控Python项目 # query -user:someuser # 排除某个用户的仓库 return query这个函数允许你灵活地调整搜索范围。created:过滤器是减少初始数据量的关键。4.3 调用GitHub API并解析结果接下来我们编写函数来执行搜索并处理返回的JSON数据。def search_github(): 执行GitHub搜索返回解析后的结果列表。 query build_search_query() params { q: query, per_page: 20, # 每页结果数最大100 page: 1 } try: response requests.get(GITHUB_SEARCH_URL, headersHEADERS, paramsparams, timeout30) response.raise_for_status() # 如果状态码不是200抛出异常 data response.json() items data.get(items, []) logger.info(f本次搜索到 {len(items)} 个结果。) return items except requests.exceptions.RequestException as e: logger.error(fGitHub API请求失败: {e}) return [] except json.JSONDecodeError as e: logger.error(f解析GitHub响应JSON失败: {e}) return []我们设置了per_page20对于监控来说足够了。response.raise_for_status()能帮助我们快速发现认证失败、速率限制等问题。4.4 实现去重机制为了避免同一个仓库被反复推送我们需要一个持久化的去重记录。这里使用SQLite数据库。def init_database(): 初始化SQLite数据库创建去重表。 conn sqlite3.connect(sent_history.db) cursor conn.cursor() cursor.execute( CREATE TABLE IF NOT EXISTS sent_items ( id INTEGER PRIMARY KEY AUTOINCREMENT, repo_full_name TEXT UNIQUE, -- 仓库全名如 owner/repo pushed_at TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ) conn.commit() conn.close() def is_item_sent(repo_full_name): 检查某个仓库是否已经推送过。 conn sqlite3.connect(sent_history.db) cursor conn.cursor() cursor.execute(SELECT 1 FROM sent_items WHERE repo_full_name ?, (repo_full_name,)) exists cursor.fetchone() is not None conn.close() return exists def mark_item_as_sent(repo_full_name): 将某个仓库标记为已推送。 conn sqlite3.connect(sent_history.db) cursor conn.cursor() try: cursor.execute(INSERT INTO sent_items (repo_full_name, pushed_at) VALUES (?, ?), (repo_full_name, datetime.now())) conn.commit() except sqlite3.IntegrityError: # 唯一约束冲突说明已存在忽略即可 pass finally: conn.close()repo_full_name例如octocat/Hello-World作为唯一标识是合理的选择因为它能唯一确定一个仓库。pushed_at字段记录推送时间便于后期清理老旧记录。4.5 格式化消息并发送微信通知将从GitHub获取的信息组织成一条对微信友好的Markdown消息并通过Server酱API发送。def send_wechat_notification(repo_info): 通过Server酱发送微信通知。 title f GitHub安全监控告警: {repo_info[full_name]} # 构建Markdown格式的消息体 desp f ## 发现相关仓库: [{repo_info[full_name]}]({repo_info[html_url]}) **描述:** {repo_info.get(description, 暂无描述)} **语言:** {repo_info.get(language, N/A)} **星标数:** {repo_info.get(stargazers_count, 0)} **最近更新:** {repo_info.get(updated_at, N/A)} **创建时间:** {repo_info.get(created_at, N/A)} --- **监控关键词:** {SEARCH_KEYWORDS} payload { title: title, desp: desp # Server酱的Markdown内容字段是desp } try: resp requests.post(SERVERCHAN_URL, datapayload, timeout10) resp_data resp.json() if resp_data.get(code) 0: logger.info(f微信通知发送成功: {repo_info[full_name]}) return True else: logger.error(fServer酱API返回错误: {resp_data}) return False except Exception as e: logger.error(f发送微信通知失败: {e}) return False消息内容包含了仓库的核心信息和一个直接跳转到GitHub仓库的链接方便你快速查看详情。4.6 主循环与调度逻辑最后我们将所有功能串联起来形成主监控函数并设置定时执行。def monitor_job(): 一次完整的监控任务。 logger.info(开始执行GitHub监控任务...) repos search_github() new_repos_count 0 for repo in repos: repo_full_name repo[full_name] # 去重检查 if is_item_sent(repo_full_name): logger.debug(f仓库 {repo_full_name} 已推送过跳过。) continue # 发送通知 if send_wechat_notification(repo): # 发送成功标记为已发送 mark_item_as_sent(repo_full_name) new_repos_count 1 # 短暂间隔避免触发Server酱或自身速率限制 time.sleep(1) logger.info(f监控任务完成。共处理{len(repos)}个仓库其中{new_repos_count}个为新仓库并已通知。) def run_scheduler(): 使用schedule库运行定时任务适用于调试。 schedule.every(CHECK_INTERVAL_MINUTES).minutes.do(monitor_job) logger.info(f监控调度器已启动每{CHECK_INTERVAL_MINUTES}分钟运行一次。) # 立即运行一次 monitor_job() while True: schedule.run_pending() time.sleep(1) if __name__ __main__: # 初始化数据库 init_database() # 方式一使用schedule库适合脚本常驻运行调试 # run_scheduler() # 方式二直接执行一次适合配合系统cron使用 monitor_job()在__main__部分我提供了两种运行方式。对于生产环境强烈推荐使用方式二即让脚本只执行一次监控任务然后通过操作系统的定时任务如Linux的cron来定期调用这个脚本。这样更稳定脚本崩溃也不会影响调度系统且能更好地管理日志和资源。5. 部署、优化与高级配置脚本写好了如何让它稳定、长期地运行起来并变得更聪明5.1 生产环境部署使用系统Cron在Linux服务器上使用cron是最可靠的方式。确保你的脚本具有可执行权限chmod x /path/to/your/github_monitor/monitor.py。你可以在脚本第一行加上#!/usr/bin/env python3来指定解释器。编辑当前用户的cron表crontab -e。添加一行例如每10分钟运行一次*/10 * * * * cd /path/to/your/github_monitor /usr/bin/python3 monitor.py /path/to/your/github_monitor/logs/monitor.log 21cd /path/to/your/github_monitor确保脚本在正确的目录下运行能正确找到config.py和数据库文件。 .../monitor.log 21将脚本的标准输出和错误输出都重定向到日志文件便于排查问题。在Windows上可以使用“任务计划程序”来创建定时任务。5.2 监控策略优化让告警更精准初始的关键词可能产生很多噪音。你需要像一个调参师一样优化你的查询。利用GitHub搜索高级语法in:name,readme,description限定搜索范围在仓库名、README和描述中避免代码文件中的偶然匹配。filename:.md只搜索特定扩展名的文件。stars:100只关注有一定流行度的项目。pushed:2023-01-01关注近期还有维护的项目。建立排除词列表在build_search_query函数中将常见的误报词加入排除。例如如果你监控password可能会匹配到很多教学项目或测试项目可以排除-tutorial、-example、-test。分主题监控不要把所有鸡蛋放在一个篮子里。可以为不同的技术栈如Kubernetes、Redis、WordPress创建不同的监控任务和关键词组合甚至使用不同的Server酱SCKEY推送到不同的群组。5.3 通知渠道扩展除了Server酱推送到个人微信你还可以集成更多渠道企业微信/钉钉机器人这些办公软件提供的群机器人API同样简单易用适合团队协作。只需将脚本中的send_wechat_notification函数替换为调用对应机器人Webhook的函数即可。邮件通知使用Python的smtplib库。虽然实时性不如即时通讯工具但适合发送摘要报告。Telegram Bot对于国际化的团队Telegram是一个不错的选择。5.4 增加健壮性与日志一个生产级的脚本必须考虑异常处理。网络重试对requests调用添加重试机制使用urllib3的Retry或tenacity库。数据库连接池如果监控频率很高可以考虑使用连接池管理SQLite连接。详细的日志分级使用Python的logging模块区分DEBUG、INFO、WARNING、ERROR等级别并配置滚动日志文件避免日志无限膨胀。import logging.handlers handler logging.handlers.RotatingFileHandler(logs/monitor.log, maxBytes10*1024*1024, backupCount5) logger.addHandler(handler)6. 常见问题排查与实战技巧在实际运行中你肯定会遇到各种问题。这里记录了一些典型的坑和解决方案。6.1 GitHub API速率限制Rate Limiting这是最常见的问题。未认证请求限制很严格认证后大幅提升。症状脚本运行一段时间后search_github函数开始返回空列表或直接抛出异常查看日志可能看到403状态码和rate limit exceeded信息。排查在脚本中添加一个函数每次请求后检查API剩余的调用次数。def check_rate_limit(): url https://api.github.com/rate_limit resp requests.get(url, headersHEADERS) limits resp.json()[resources][search] logger.info(f搜索API剩余次数: {limits[remaining]}/{limits[limit]}, 重置时间: {limits[reset]})在monitor_job中调用它。解决确保正确使用了Personal Access Token并且Token在请求头中设置正确。降低监控频率将CHECK_INTERVAL_MINUTES从10分钟调整为30分钟或更长。优化查询使用更精确的关键词减少per_page数量避免不必要的API调用。6.2 Server酱通知发送失败症状脚本运行正常但微信收不到消息。排查检查SCKEY确认config.py中的SERVERCHAN_SCKEY是否正确且没有过期。可以手动在浏览器访问https://sctapi.ftqq.com/{SCKEY}.send?titleTestdespHello测试。检查网络脚本运行的环境是否能正常访问外网。查看Server酱日志登录Server酱官网在“消息记录”里查看是否有发送记录及失败原因。解决根据错误信息调整。常见原因是标题或内容过长Server酱有字数限制。可以适当截断描述信息。6.3 去重机制失效收到重复通知症状同一个仓库被反复推送。排查检查数据库查看sent_history.db文件中的sent_items表确认记录是否成功插入。检查唯一标识确认我们使用的repo_full_name是否足够唯一。如果监控的是同一个仓库下的不同分支或Tag可能需要结合latest_commit_sha等字段来生成唯一ID。解决确保mark_item_as_sent函数在成功发送通知后才被调用。考虑增加更复杂的去重逻辑例如“24小时内不重复推送同一仓库”。6.4 脚本在Cron中不执行或执行错误症状手动运行脚本正常但cron不触发或没有产生日志。排查环境变量Cron执行的环境与用户Shell环境不同可能找不到python3命令。使用which python3获取绝对路径并在cron命令中使用该绝对路径。文件路径Cron的当前工作目录通常是用户的家目录。务必在cron命令中使用cd切换到脚本所在目录或所有文件路径都使用绝对路径。权限问题确保cron任务所属用户有权限执行脚本、写入数据库文件和日志文件。解决在cron命令中将错误输出重定向到日志文件如2 /path/to/error.log是诊断这类问题的黄金法则。6.5 信息过载与误报管理监控初期误报是常态。技巧不要追求一步到位。运行脚本几天收集一批推送消息。仔细分析哪些是真正的漏洞情报哪些是无关内容。将无关内容中的特征词如项目作者名、常见教学项目名、无关技术栈名提炼出来作为排除词-keyword加入到搜索查询中。这是一个持续迭代的过程你的监控脚本会随着你的调教变得越来越“聪明”。这套GitHub监控脚本就像为你量身定制的安全雷达其价值不在于代码本身有多复杂而在于它为你打开了一扇主动获取安全情报的窗口。从配置好第一个关键词并收到微信“叮”的一声提醒开始你就已经走在了很多人的前面。剩下的就是根据你的实际需求不断地打磨搜索策略、优化通知格式让它真正成为你工作流中不可或缺的一部分。