欢迎新项目合作 加纳阿克拉
所有写作

网络安全 · 身份验证 · OAuth · 最小权限

锁定后门:我如何保护我的网站的管理员登录

谁控制一个网站的管理员面板就控制了每个访客看到和运行的内容。以下是我如何在没有服务器可以入侵的情况下使管理员工作,其密钥只打开一扇门,以及我仍需要关闭的供应链缺口.

由 Festus Boamah · · 4 min read

网站的前门是访客看到的部分。 管理员面板是后门,攻击者知道这一点。谁控制管理员就控制了每个访客阅读的内容,在最坏的情况下,还控制了在他们的浏览器中运行的代码。一个被篡改的首页是令人尴尬的,但一个首页悄悄地向每个访问者提供恶意脚本则更糟糕.

所以,当我在这个网站添加编辑器时,以便我可以从手机发布帖子而不需要打开代码编辑器时,我将其登录视为整个系统中最敏感的部分.

管理员登录流程图:浏览器中的CMS请求一个小的auth Worker登录,Worker将用户发送到GitHub以获取一个随机的状态值,GitHub返回一个代码,Worker仅将令牌返回到festusboamah.com。每次保存都成为main的Git提交

决定 1:没有服务器可以入侵

大多数内容管理系统都运行在具有用户和密码数据库的服务器上。该服务器成为目标:过时的插件、弱密码或暴露的数据库都可以将整个网站交给攻击者.

这个网站没有这些。它是一个静态网站,每当发生更改时都会从 GitHub 的 Git 存储库重新构建。管理员面板(Sveltia CMS)只是一个在我的浏览器中运行并直接与 GitHub API 通信的 JavaScript 应用程序。保存帖子意味着进行提交.

这转变了安全问题。没有管理员服务器需要修补,也没有密码数据库需要泄露。 真正的问题变成了:谁可以写入存储库? 其他一切都遵循这个答案.

决定 2:打开一扇门的密钥

要写入存储库,CMS 需要来自 GitHub 的令牌。获取令牌的简单方法是具有 repo 范围的经典 OAuth 应用。但是,该令牌将能够读取和写入 我账户可以访问的每个存储库,无论是公共还是私有的。如果它泄露,损害将远远超出这个网站.

相反,登录通过 GitHub 应用 进行:

  • 仅安装在此存储库上,并且
  • 被授予 一个权限:内容(读取和写入)。它无法触摸问题、设置、工作流程、其他存储库或我的帐户.

这是 最小权限 的原则:为每个密钥赋予尽可能小的权限,以便在(而不是如果)出现问题时,爆炸半径很小.

决定 3:保护交接

OAuth 登录流程通过一个小的 Cloudflare Worker(开源的 sveltia-cms-auth,未经修改地 vendored 到我的存储库中,具有固定提交,因此我知道确切的代码运行)。它将 GitHub 应用的客户端密钥作为加密的 Worker 密钥持有,从不在代码或存储库中。其中三个检查值得解释,因为每一个都阻止了真正的攻击:

  • 域名允许列表。 Worker 只为 festusboamah.com 启动登录。将我的管理员页面复制到其自身域名的人无法使用我的登录助手.
  • CSRF protection with state. Before sending me to GitHub, the Worker generates a random value, stores it in an HttpOnly, Secure cookie, and passes it to GitHub as the OAuth state parameter. When GitHub sends me back, the two must match. This stops an attacker from tricking my browser into finishing a sign-in that they started.
  • 来源检查的交付。 令牌通过postMessage交付给CMS窗口。Worker仅在请求窗口的浏览器报告的来源是我的域时才释放它。该来源由浏览器设置,而不是由页面设置,因此无法伪造。

决策 4:使每个更改都有责任

因为每次保存都是对 main 的 Git 提交,所以网站的每个更改都有作者、时间戳和确切的差异。 从 STRIDE 的角度来看,这解决了 否认 问题:网站上的任何更改都有记录。 这也给我带来了即时恢复:如果曾经发布了不良内容,我可以撤销提交,Cloudflare Pages 将在大约一分钟内重建干净的版本。

管理员页面还具有自己的 HTTP 头:一个单独的内容安全策略,X-Robots-Tag: noindex 以防止搜索引擎列出它们,以及 Cache-Control: no-store 以防止缓存任何敏感信息。

哪里仍然存在弱点

  • CMS 脚本没有完整性检查。 管理员页面从 unpkg CDN 加载 Sveltia CMS。 我固定了一个确切的版本,这可以防止意外升级,但没有 子资源完整性 哈希值。 如果 CDN 被破坏并提供了修改后的文件,我的浏览器将在可以访问有效的 GitHub 令牌时运行它。 这是一个经典的 软件供应链 风险。 解决方法是为脚本标签添加 integrity 哈希值或从自己的域名提供文件。
  • 管理员 CSP 比网站的宽松。 为了加载 CMS,管理员策略允许从 unpkg 加载脚本和内联脚本。 这意味着,如果管理员中存在注入漏洞,策略将无法阻止从 unpkg 的任何包中运行脚本。 自托管 CMS 也可以让我收紧这一点。
  • 一切都依赖于一个 GitHub 账户。 应用程序限制了令牌可以执行的操作,但谁控制我的 GitHub 账户就控制了网站。 这使得该账户上的强两因素身份验证(理想情况下使用通行密钥或安全密钥而不是 SMS)成为整个设计中最重要的控制措施,尽管它不在代码中。

教训

这里最有用的安全决策并不是一个巧妙的检查。 而是 完全移除服务器,然后使剩下的关键尽可能狭窄。 在添加防御之前,总是值得问:我可以移除什么,以便有更少的东西需要防御?

发现了我错过的东西? 告诉我。

Festus Boamah

一起合作

一起打造有意义的东西。

有产品要开发、品牌要塑造,或课程要开设?很期待听到你的想法。