如何安全地为 Homebrew 替换中国大陆镜像源

关于 Homebrew 在国内换源的文章一搜一大把,洒家并不想复制镜像的帮助页面再制造一篇,而是想讨论一下如何在不降低1安全性的情况下使用镜像源。

TL;DR

首先你需要有个梯子。

建议优先选择使用 HTTP 协议的代理服务器 URL,例如 http://127.0.0.1:7890。如果使用 SOCKS5 协议,在输入 URL 时,建议以 socks5h:// 开头,例如 socks5h://127.0.0.1:1080。

请输入代理服务器 URL:

{{ errorMessage }}

运行下面的命令修改 Git 全局配置,让 Git 通过代理访问 github.com:

根据使用的 shell 类型和配置,将下面一行添加到 ~/.zshrc 或 ~/.bash_profile 等配置文件末尾,设置 shell alias。

最后重新打开一个终端窗口,加载最新的 alias 配置,即可开始使用 Homebrew。

概述

“Homebrew”的意思是家酿啤酒。毕竟是家酿的,Homebrew 还是比不上 APT 这种正规的包管理器,很多设计相当凑合。它有以下特点:

一般换源都是同时替换索引镜像(Homebrew、Homebrew Core、Homebrew Cask)和二进制预编译包镜像。洒家作为受迫害妄想症搞信息安全的,马上就想到这里面显然有供应链攻击的可能性。如果镜像被黑客劫持,或者镜像自身作恶,那么用户的电脑就白给了。

如果你:

那么可以使用本文介绍的方法换源。

换源

总体来看,一共有 3 类资源需要分别处理:

Git 设置代理

查阅文档,并动手实验验证,可以证实 Git 不存在 https.proxy 配置项,只需配置 http.proxy 即可同时控制 HTTP 和 HTTPS 协议的代理。网络流传的一些方法是错误的。

首先使用原版方式安装 Homebrew,不要替换 Git 仓库的 origin。此时 Git 仓库的 origin 是 GitHub,需要给它们加上代理。以下两种方式任选一种即可。

全局设置

一般我们可以全局设置 Git 使用 HTTP/HTTPS 协议访问 GitHub 时使用代理。假设代理服务器 URL 为 http://127.0.0.1:7890,运行下面的命令即可。

git config --global http.https://github.com.proxy http://127.0.0.1:7890

逐个仓库设置

如果你不想修改全局设置,也可以逐个仓库设置代理。

列出所有需要处理的 Git 仓库:

brew --repo
for repo in $(brew tap)
do
    brew --repo "${repo}"
done

假设代理服务器 URL 为 http://127.0.0.1:7890,逐个 cd 进去,运行:

git config --local http.proxy http://127.0.0.1:7890

配置 Homebrew

上文提到,Homebrew 没有配置文件,我们要用到的各种配置全都需要通过环境变量设置。假设代理服务器 URL 为 http://127.0.0.1:7890,根据使用的 shell 类型和配置,将下面一行添加到 ~/.zshrc 或 ~/.bash_profile 等配置文件末尾,设置 shell alias。然后需要重新打开一个终端窗口,加载最新的 alias 配置。

alias brew='HOMEBREW_NO_BOTTLE_SOURCE_FALLBACK=1 HOMEBREW_NO_UPGRADE_AUTO_UPDATES_CASKS=1 HOMEBREW_BOTTLE_DOMAIN=https://mirrors.ustc.edu.cn/homebrew-bottles HOMEBREW_CLEANUP_MAX_AGE_DAYS=15 no_proxy=".cn" http_proxy="http://127.0.0.1:7890" https_proxy="${http_proxy}" ALL_PROXY="${http_proxy}" brew'

其中设置了一些个人认为有用的参数:

PoC

下面来证明控制了索引和 bottle 源镜像就控制了一切。

环境版本信息:

模拟索引仓库被控制的场景,创建一个 formula 文件 /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core/formula/phuker-test.rb,内容为:

class PhukerTest < Formula
  desc "PoC"
  homepage "https://www.example.com/"
  url "http://127.0.0.1:8181/phuker-test-1.0.0.tar.gz"
  sha256 "0000000000000000000000000000000000000000000000000000000000000000"

  bottle do
    rebuild 1
    sha256 "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" => :catalina
  end

  def install
    system "make", "install"
  end
end

创建一个简单的 .tar.gz bottle 文件,此文件的 SHA-256 hash 需要填到 .rb formula 文件的对应位置。文件结构为:

$ cd /tmp/brew/homebrew-bottles/bottles/
$ tar -tvf phuker-test-1.0.0.catalina.bottle.1.tar.gz
drwxr-xr-x  0 phuker   wheel       0 11 18 09:59 phuker-test/
drwxr-xr-x  0 phuker   wheel       0 11 18 09:59 phuker-test/1.0.0/
drwxr-xr-x  0 phuker   wheel       0 11 18 09:59 phuker-test/1.0.0/bin/
-rwxr-xr-x  0 phuker   wheel      23 11 18 09:59 phuker-test/1.0.0/bin/phuker-test

.tar.gz bottle 文件中的 phuker-test/1.0.0/bin/phuker-test 可执行文件内容为:

#!/bin/sh

echo 'Pwn!'

启动 Web 服务,模拟 bottle 镜像源:

$ cd /tmp/brew
$ python3 -m http.server --bind 0.0.0.0 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
127.0.0.1 - - [18/Nov/2020 10:00:04] "GET /homebrew-bottles/bottles/phuker-test-1.0.0.catalina.bottle.1.tar.gz HTTP/1.1" 200 -

安装并执行命令:

$ export HOMEBREW_NO_AUTO_UPDATE=1
$ export HOMEBREW_NO_BOTTLE_SOURCE_FALLBACK=1
$ export HOMEBREW_BOTTLE_DOMAIN=http://127.0.0.1/homebrew-bottles
$ /usr/local/bin/brew install phuker-test
==> Downloading http://127.0.0.1/homebrew-bottles/bottles/phuker-test-1.0.0.catalina.bottle.1.tar.gz
======================================================================== 100.0%
==> Pouring phuker-test-1.0.0.catalina.bottle.1.tar.gz
🍺  /usr/local/Cellar/phuker-test/1.0.0: 2 files, 878B
$ phuker-test
Pwn!

Q.E.D.

扩展阅读

Footnotes
  1. “不降低”指的是换源后,安全性和不换源相同。原始源仓库被上传恶意代码、SHA-256 hash 碰撞攻击等场景则不在本文讨论范围之内。↩


Comments

您可以匿名发表评论,无需登录 Disqus 账号,勾选“我更想匿名评论”后,姓名和电子邮件分别填写“匿名”和“someone@example.com”然后发表评论即可。您也可以登录 Disqus 账号后发表评论。您的评论可能需要经过我审核后才能显示。点赞投票按钮(Reactions)无需登录即可点击。Disqus 评论系统在中国大陆可能无法正常加载和使用。

Statements

Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License

本作品采用知识共享署名—非商业性使用—禁止演绎 4.0 许可协议国际版(CC BY-NC-ND 4.0)进行许可。

This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License (CC BY-NC-ND 4.0).

Written by a Human, Not by AI

本作品由人类创作,整体内容符合 Not By AI 90% 规则。人工智能工具仅在有限程度上提供插图生成、代码生成、灵感启发、错别字与语法检查,以及在不改变原意的前提下进行润色。所有修改在采纳前均经过作者仔细审阅。

This work was created by a human author, and the overall content complies with the Not By AI 90% Rule. Artificial intelligence tools were used only to a limited extent for illustration generation, code generation, idea inspiration, typo and grammar checking, and stylistic refinement without altering the original meaning. All modifications were carefully reviewed by the author before being adopted.

Top