本文基于自建私有 Gitea 场景整理,不同版本、不同部署方式(Docker / systemd / k8s)菜单位置和命令细节可能略有差异。
文中涉及账号删除与数据库查询,请务必先备份数据库和关键配置后再执行。
如果你不清楚某条命令会造成什么影响,请先在测试环境验证,再操作生产环境。
今天遇到一个有点恶心的问题:团队私有 Gitea 遭遇自动化恶意注册,短时间出现大量异常账号,并伴随批量建仓行为,管理后台直接被刷屏。
先说结论:这次能比较快处理掉,关键不是「删得快」,而是顺序对——先止血,再筛选,最后批量清理。
事件现象
我这边最先看到的是两个信号:
- 用户列表里突然多了很多陌生账号
- 仓库列表出现了批量新建的可疑仓库
这时候第一反应不要急着逐个点删除。因为你删一批,对面还能继续注册一批。而且我发现的时候攻击者已经注册了上千个账号,只会越删越多。 先把入口关掉,才有资格谈清理。
应急目标
这次处置目标我定得很明确:
- 立刻止血:阻断新的垃圾账号进入系统
- 精准识别:尽量区分团队真实账号和异常账号
- 批量清理:把 Spam 账号快速、安全地删掉
- 降低复发:补上最基本的防护和告警
Part 1:先止血(关闭自助注册)
我第一步先去 Gitea 管理配置里禁用了用户自行注册(Disable Registration)。 这一步完成后,攻击面会立刻收缩,系统不再持续「进脏数据」。
你可以把这一步理解为:先拧紧水龙头,再擦地板。 不然地板永远擦不干净。
打开 Gitea 的配置文件 app.ini(通常位于 /etc/gitea/app.ini 或 Docker 映射的 data/gitea/conf/app.ini),找到 [service] 区块
如果你只是个人使用或团队内部使用,建议直接关闭外部注册,后续由管理员在后台手动创建账号:
[service]DISABLE_REGISTRATION = true如果必须允许外部注册,请开启以下选项来拦截自动化脚本:
[service]; 要求邮箱验证 (需要配置好 SMTP 邮件服务)REGISTER_EMAIL_CONFIRM = true
; 开启验证码ENABLE_CAPTCHA = true; 验证码类型:可选 image, recaptcha, hcaptcha, mcaptcha, cfturnstile 等CAPTCHA_TYPE = image
; 开启人工审核,新注册账号必须由管理员在后台激活才可用REGISTER_MANUAL_CONFIRM = true修改完毕后,请重启 Gitea 服务以使配置生效。可以通过管理后台的 站点管理 -> 配置 直接查看是否已正确加载。
建议顺手检查:
- 是否真的「保存并生效」
- 前台注册页是否已不可用
- 是否还有第三方入口能绕过注册策略
Part 2:数据库筛选异常账号(基于邮箱域名白名单)
我们团队成员邮箱相对固定,主要是:
qq.comfoxmail.com163.com
所以我用了一个非常实用的思路:
先保留白名单域名账号,再把剩余账号作为「待复核对象」。
WARNING注意:白名单策略只能提高效率,不能替代人工复核。
某些机器人账号、历史测试账号、外协账号可能不在白名单里,直接删除有误伤风险。
在导出后请务必检查所有正常用户的 id 是否被排除,我在这一步就险些删掉了我们用 163 邮箱和 foxmail 邮箱注册的两个正常账号。
下面给一个参考的 SQL 思路(这里我是 sqlite,请按你的数据库类型调整):
# 如果是 Docker 部署,请进入容器内执行该命令,或者根据宿主机的 volume 映射路径进行操作。
# 首先备份数据库防止误删cp /var/lib/gitea/data/gitea.db /var/lib/gitea/data/gitea.db.bak
# 排除所有已知团队邮箱后缀,筛选疑似异常账号,并写入到 ids.txt 中sqlite3 /var/lib/gitea/data/gitea.db "SELECT id FROM user WHERE email NOT LIKE '%@qq.com' AND email NOT LIKE '%@foxmail.com' AND email NOT LIKE '%@163.com' AND is_admin = 0 AND type = 0 AND id > 0;" > ids.txt我这里就是先查、先导出、先复核,没有在数据库里直接 DELETE。
原因很简单:账号删除最好走 Gitea 自己的管理逻辑或 CLI,减少关联数据残留和不可预期问题。
Part 3:用 Gitea CLI 批量删除 Spam 账号
筛选出待处理账号后,我用 Gitea CLI 做批量删号。 这样比手点后台快很多,也更可重复执行。
# 逐行读取 ids.txt 并执行删除操作for id in $(cat ids.txt); do echo ">>> 正在处理用户 ID: $id" # 使用 --purge 参数强制连带仓库一起删除 docker exec -u git gitea gitea admin user delete --id "$id" --purgedoneecho "清理完成!"如果你没有使用 docker 而是直接使用宿主机直接部署:
# 在执行下面的循环删除前,强烈建议先 `vi ids.txt` 看一眼,确保里面没有你眼熟的正常同事的 ID,要不然就真的要「运维祭天」了for id in $(cat ids.txt); do echo ">>> 正在处理用户 ID: $id" # sudo -u git 表示以 git 用户的权限运行 gitea 命令 # 如果提示找不到配置文件,需要在命令后追加 --config /etc/gitea/app.ini (替换为实际路径) sudo -u git gitea admin user delete --id "$id" --purgedoneecho "清理完成!"然后就可以切到管理面板看烟花了(笑)
清理后我做了哪些验证
批量删除跑完不代表结束,我又做了几步确认:
- 用户总量是否回到合理区间
- 可疑仓库是否已经同步清理(必要时再做仓库侧处理)
- 团队成员账号登录是否正常
- 管理后台无持续新增异常账号
- 日志中没有新的批量注册行为
只有这几项都正常,我才把这次应急判定为「处理完成」。
环境差异:为什么另一实例暂时没中招
我这次也检查了另一套 Gitea。那套虽然当时允许注册,但因为放在域名反代后,且只允许本地网络访问(没有公网直接入口),目前没有出现类似的 Spam 注册和批量建仓问题。
这件事给我的提醒是:
- 是否开放注册,决定「能不能注册」
- 是否暴露公网入口,决定「谁能来注册」
- 反向代理和访问控制策略,决定「攻击面有多大」
也就是说,开放注册本身不一定马上出事;但「开放注册 + 公网可达 + 缺少限流/校验」这个组合,风险会明显上升。
虽然另一实例暂时没遇到问题,我还是把它的注册入口也关了。与其等流量打过来再救火,不如先把默认攻击面降下来。
这次踩坑里最容易忽略的点
1) 不要上来就数据库硬删
直接在数据库 DELETE 很爽,但容易留下关联问题,比如不能清理干净仓库、issue 等数据,容易残留。
账号体系尽量通过 Gitea 管理面或 CLI 做删除,让应用层处理关系数据。
2) 白名单不是绝对安全
域名只是快速过滤条件,不是身份认证。 能人工复核的尽量复核,尤其是管理员、机器人账号和历史服务账号。
3) 先止血比「删得快」更重要
攻击还在持续时,清理动作的边际收益很低。 先封入口,后清场,这个顺序别反。
事后加固(防止再来一轮)
为了防止攻击者复活的风险,我们可以:
- 保持关闭开放注册,改为邀请制/管理员审批
- 开启并强化验证码、人机校验、邮件验证
- 检查管理员账户口令强度与 2FA
- 检查是否暴露了不必要的公开入口
- 尽量通过反向代理、不使用默认的 3000 端口作为入口
- 定期检查账号情况,遇到异常情况及时清理
总结
这次事件本质上不复杂,但很考验应急顺序。 我的经验可以浓缩成一句话:
先止血、再筛选、后清理,最后补防线。
如果你现在也在维护私有 Gitea,我建议你今天就做两件事:
- 检查是否还开着公开注册
- 跑一次异常账号巡检
提前 10 分钟排查,可能能省掉你后面 3 小时救火。