abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · gitlab-gitea-import-ssrf
GitLab CE 19.4.1 - GitLab Inc.
Gitea 导入只调用一次 UrlBlocker.validate!,随后对于 https 会丢弃已固定 IP 的 URI,并把原始主机名交给 Faraday。Octokit 的 UrlValidation 会再次校验,但不会重写 env[:url]。在这两次查找之间重新绑定该名称,GitLab 就会像访问 Gitea 一样对回环地址发起 GET 请求。仓库列表 JSON 会通过 GET /import/gitea/status.json 返回。HTTP 导入会保留 IP 固定。直接访问 https://127.0.0.1 会被阻止。
已登录用户只要能在其个人命名空间打开 Gitea 导入,就能让 GitLab 请求一个他们重新绑定到回环地址的主机,并获取响应体。
| ID | 暂无 CVE |
| CWE | CWE-918、CWE-367 |
| CVSS | 高危:7.7 CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:H/I:L/A:N |
| 产品 | GitLab CE |
| 受影响版本 | 19.4.1-ce.0(v19.4.1-ee 树上的 26212baacadb) |
| 认证 | 已认证用户、Gitea 导入、个人命名空间 |
| 许可证 | GNU Affero GPL v3.0 |
| 实验环境 | 仅 127.0.0.1 |
将 Gitea Host URL 设置为 https://<他们控制的名称>/。UrlBlocker 允许第一条公共 A 记录。HTTPS 的 client_options 会保留该主机名。重新绑定到 127.0.0.1。Faraday 会在 GitLab worker 的回环地址上对 catcher 发起 GET 请求。status.json 中的 provider_repos 就是 catcher 返回的内容。
在 all-in-one omnibus 上,那就是 Workhorse、Puma、各类 exporter,以及 worker 上监听的任何其他服务。不是主机 RCE。不是未认证。HTTP Host URL 仍会停留在固定 IP 上。
同一产品,同类遗留问题:Gitaly fetch 省略 resolved_address、webhook 0.0.0.0/8。
我先阅读了 2026 年 9 月 23 日 19.4.1 / 19.3.3 / 19.2.7 的补丁说明,然后查看了 v19.4.1-ee 代码树。CI 正则 RCE、MR diff XSS、Workhorse file.path 读取在该版本上均已修复。导入 SSRF 尚未处理完。
Import::GiteaController#client_options 就是遗留问题:
host: verified_url.scheme == 'https' ? provider_url : verified_url.to_s,
HTTP 使用 verified_url(IP)。HTTPS 使用 provider_url(用户输入的名称)。LegacyGithubImport::Client 基于该主机构建 Octokit 的 api_endpoint。Gitlab::Octokit::UrlValidation 调用 validate!,然后以 @app.call(env) 调用,未做任何重写。
我搭建了原版 gitlab/gitlab-ce:19.4.1-ce.0。关闭了本地请求。由于 Faraday 的 ssl.verify 为 true,将实验 CA 放入 /etc/gitlab/trusted-certs。gitea.lab 的 DNS:第 1-2 次查询返回 1.1.1.1,之后的查询返回 127.0.0.1。Catcher 与 GitLab 共享 netns,监听 :443。GET /import/gitea/status.json 返回了名为 GITLAB-GITEA-IMPORT-SSRF-WITNESS 的 provider_repos。Catcher 日志:GET /api/v1/user/repos peer=127.0.0.1 host=gitea.lab。
直接访问 https://127.0.0.1/:提示 "Specified URL cannot be used"。HTTP http://gitea.lab/ 停留在固定 IP 上,未返回 witness。
已记录的弯路:带 Accept: application/json 访问 /users/sign_in 返回 404(HTML 为 200);种子密码 CveLabRoot9! 因包含 root 被拒绝;CA 以 ro 方式 bind-mount 后,在 reconfigure 链接该文件后出现 EROFS;在第 2 次查询时先返回前两条再返回回环地址,会导致 UrlValidation 在 Faraday 连接前就阻止。最终成功的是 pairs 模式(奇数次返回公共地址,偶数次返回回环地址)。反弹 shell。花架子。判定依据是 status.json 中的 witness 加上 catcher 的对端地址。
cd lab
./run.sh
目标仅限 http://127.0.0.1:18400。GitLab CE 需要数 GB 内存。首次启动很慢。仅限授权实验环境。
status.json status=200 ... provider_repos ... GITLAB-GITEA-IMPORT-SSRF-WITNESS
catcher GET /api/v1/user/repos peer=127.0.0.1 host=gitea.lab status=200 witness=True
negative-a https://127.0.0.1 blocked
negative-b http://gitea.lab no-witness
SUCCESS GITLAB-GITEA-IMPORT-SSRF status.json mode=pairs http=200 GITLAB-GITEA-IMPORT-SSRF-WITNESS
def client_options
verified_url, provider_hostname = verify_blocked_uri
{
host: verified_url.scheme == 'https' ? provider_url : verified_url.to_s,
api_version: 'v1',
hostname: provider_hostname
}
end
对 https 也像 HTTP 已经做的那样使用 verified_url.to_s。在 Octokit 的 UrlValidation 中将 env[:url] 重写为固定 IP。
gitea_controller.rb · legacy_github_import/client.rb · octokit/url_validation.rbGNU Affero GPL v3.0。参见 LICENSE。仅限回环实验环境。不提供任何担保。