公共头像服务 Gravatar 的使用及其安全隐患

陪她去流浪 桃子 编辑 阅读次数:18441

所谓 Gravatar 的公共可识别头像服务(Globally Recognizable Avatar),就是用户通过自己的邮箱注册一个 Gravatar 的帐号,然后给此帐号上传一个自己的头像图片。 于是 Gravatar 就建立了一个全球性的“邮箱 ↔️ 头像”的关联库。 任何需要通过用户邮箱展示其头像的地方(比如:发表社交评论),都可以通过用户邮箱来向 Gravatar 获取。 这样就简化了用户需要在不同的站点上维护相同头像的麻烦。 Gravatar 有被大量广泛地使用于各种博客/个人站点/WordPress/GitHub 等。

Gravatar 的简单用法

非常简单,访问类似:https://secure.gravatar.com/avatar/{HASH} 的链接即可。 其中的 {HASH} 是通过标准的算法根据用户的邮箱计算得来的哈希值:md5(email) / sha256(email)avatar 比如我的头像链接是: https://secure.gravatar.com/avatar/09fe7907900043edaa672b7a2620543b。 还有一些其它的查询参数可以设置,比如大小、默认头像、头像分级等等。不在本文今天的讨论范围内,可参考:Image Requests – Gravatar Developer Docs

本地化

另外,如果你有“你自己的网站可以访问,但是头像却加载不了”的问题,可以参考:本地化。 关于为什么要本地化

自己做代理是因为早在十年前玩的 WordPress 的时候我就发现,官方的 Gravatar 在中国经常不能访问,但是我自己的网站却能访问,外加第三方的代理站也是经常不能访问/不稳定,所以从那时候开始我就萌生了自己代理的念头,毕竟可以和我自己的网站本身“共生死”,几个头像流量也不大。没有理由不这样做。

上面的参考链接差不多是🔟年前的代码了。Gravatar 的请求响应头部会包含一些可能包含哈希的字段,比如:

  • Link: link: <https://gravatar.com/avatar/09fe7907900043edaa672b7a2620543b>; rel="canonical"
  • Content-Disposition: content-disposition: inline; filename="09fe7907900043edaa672b7a2620543b.png"

请记得去掉,最好用白名单过滤一下就好了。

隐私与安全性㊙️

为了安全与隐私,链接中肯定是不能直接将用户的邮箱带上的,所以最容易想到且实际也被大量运用的方案就是“使用哈希后的结果”。 尽管 md5/sha256 名义上是摘要算法,不可逆,但是也免不了仍然被人们经常称其为“MD5 加密/解密”。 解密的过程并不是真正通过复杂的数学计算🧮,而仅仅是预先创建了一个 哈希 ↔️ 邮箱 的关联表,直接查表得出结果。这个表被称为“彩虹表🌈”。

而预先创建这个表需要的邮箱列表通常就是 爬取各种社区数据库/泄漏数据库/随机构造常用的 等方式得来。 一旦有了这个列表,md5/sha256 的结果也就有了。 可能是由于 md5 已经很容易被“解密”,所以 Gravatar 可能是后来把算法切换成了 sha256(没考证具体原因,反正我记得早期确实是只支持 md5 的)。 但是我觉得这完全💯没有任何用处。

随便在网上搜索🔍一下“MD5 解密”/“SHA256 解密”就能出现很多网站。比如这个:Hashes。 你可以试试把我的邮箱的哈希(09fe7907900043edaa672b7a2620543b)粘贴上去看看我的邮箱📪是啥(“你能看见的都是我想让你看见的。”)。

然而,在当今 WordPress 占据半壁江山的时代,包括所有使用 Gravatar 作为用户头像的个人网站/博客,用户的邮箱就这样地泄露了。你可以随便找一个个人站点试试看评论区的头像。

我认为 Gravatar 也没有更好的办法解决这个问题。

我能做的🥵

为了实现我博客发表评论时“邮箱(不公开)”的承诺,我做了我自己的努力。

早期我也是直接用哈希(2015年)的,但是后来就改成了内部接口(2020年)。 接口从 /v2/avatar/{HASH} 换成了 /v3/comments/{ID}/avatar。 后面的 {ID} 是评论的编号(注:因为我的博客目前没有“用户系统”,所以我只能根据评论编号来区分用户,否则我完全可以直接用“用户编号”来代表一个用户。),内部通过此编号反查出用户的真实邮箱,再内部代理转发 HTTP 请求获取头像。 前端不可能根据评论编号拿到用户的邮箱📮,这样就真正地保护了用户的隐私安全/我自己的数据安全

你真的可以选择相信我而大胆地写你真实的邮箱地址,除了生成头像以及接收评论回复↩️外,不会有任何其它用途。

改接口后的性能/资源优化

二狗那里 我发现我这个 /v3/comments/{ID}/avatar 请求不够好:

另外,昨天在跟你反馈这个问题后,我发现一个小细节/性能问题:多个评论可能是同一个人的头像,如果根据评论ID来生成头像,则强制把它们区别开了,实际上图片是同一张,这会导致请求重复/浪费网络流量。哈哈哈哈。所以我昨天简单改了下(上上面的那张图/API废弃了),内部根据邮箱生成头像“编号”返回给前端,类似一对一映射,这样请求会大量减少。

所以我再次把接口改了(2024年)/v3/comments/{ID}/avatar ➡️ /v3/avatar/{ephemeral}

内部会为每个邮箱生成一个可在服务重启后也尽量保持一致的、非常简单/值又小的“短生/临时(ephemeral)编号”,然后用此编号反查出用户邮箱。 和最开始使用 md5/sha256 类似,只不过这次是我用我自己选择的简单生成算法,越简单别人就越不能根据生成的值(ephemeral)反推出邮箱。

至此,一切都刚刚好。

标签:博客日志 · 安全 · 密码学

文章评论 37 发表评论 登出
  1. dolingou https://www.dolingou.com

    原来gravatar还有这种安全隐患... 都没有想到过。

    1. 桃子

      一眼看成了多邻国[狗头]

      1. dolingou https://www.dolingou.com

        今天你学习了吗!

        1. 桃子

          连环夺命催!

    2. 雨帆 https://yufan.me

      晚上又重新看了一下这篇文章,觉得讲得还是很有道理的,于是我花了 20 分钟把我的博客评论的头像也小小隐藏了一下。不过我直接用的是 user id 这种数字字段去做的,比较懒得设计一个什么 hash 了。

      https://github.com/syhily/yufan.me/pull/53

      1. 桃子

        是有,有用户 ID 就直接用了,最方便省事。可惜我的博客目前没有真正的用户系统。

        1. 桃子

          说个奇怪的现象,那天二狗回复了你的关于合肥的那篇文章后,我发现,在我的电脑上,就他的头像一直是错的,长下面这样,也不知道是为什么🤔。

          图片来自:https://yufan.me/avatar/1690.webp

          就算我在完全没有代理的小主机上用 curl 请求,也是错误的,非常神奇,那天我们还专门研究了一下,没找到原因🤪。

          1. 雨帆 https://yufan.me

            233,我用的是 weavatar 的服务,很有可能是这个服务的问题,它对于一些可能敏感的头像,会自动屏蔽。

            1. 桃子

              但是但是,二狗那里显示没有问题……说明有可能不是头像本身的问题🥹,我们找了一会儿原因,没有结论,就放弃了。

              1. Doghole

                我这也有问题了

                1. 桃子

                  🤣🤣🤣🤣🤣

            2. 雨帆 https://yufan.me

              经过仔细测试,确认是 weavatar 的问题,目前已经替换了镜像地址,应该暂时解决了这个问题。

              1. 桃子

                哈哈哈,一个猜想:是不是二狗的头像太五彩斑斓了,被某种不严谨的算法识别成了类似彩虹🌈旗的东西,被认为是政治不正确了,然后因为使用的代理所在的区域不一样,有的地方正确显示,有的地方不能。如果真的是这样的话,也太狗血了。 [狗头][狗头][狗头]

                1. dog

                  看着评论区自己的头像陷入了沉思

                  1. 桃子

                    哈弗大狗。

            3. 雨帆 https://yufan.me

              感觉本地化最大的问题是,如果更新了头像并不能及时更新。

              1. 桃子

                不会的。直到几天前我都还是直接原样地转发的官方请求,我本身并没有缓存这个头像。官方是 cache-control 5️⃣分钟,我转发给浏览器那也是5️⃣分钟。

                这几天我才发现5️⃣分钟实属没必要这么频繁,才覆盖了官方的时长,延长到了几天。 在浏览器上强制刷新页面也可以让缓存失效。所以我觉得这不算太大的问题。

                1. 雨帆 https://yufan.me

                  其实我 1 个小时前基本看完了你的博客代码,大概清楚你这块的实现了。 --包括当时看到 KaTeX 时饶有兴致地看了看底层是怎么实现的 🫣。-- 如果是为了安全,避免评论者头像地址导致被逆向出 Email 的话,确实你现在这个实现方式还阔以。不过我使用的 FaaS 服务流量就要双倍计费,所以当下还是直接一个 Mirror Site 完事。(逃

                  1. 桃子

                    哈哈哈😂,这是我近🔟年慢慢从零开始学写前端写来的博客代码,竟然1️⃣个小时就给看!完!了!!

                    我现在不怕流量了,自从上次被恶意攻击过两次后我就加了监控,有大流量的话,我几分钟内就能知道。VPS 自带的流量我完全用不完,虽然只有 600G,并且我家里全部外网流量都走它。

                    1. 雨帆 https://yufan.me

                      代码结构还好,我主要看了我关心的几个部分,如文章的渲染解析、评论系统、主题实现,很坦诚地说,代码整体结构并不复杂,很清晰,(比我写的好上太多)所以直接都是一眼过。

                      不过感觉还多东西,如果用 Node.js 生态,可能比 Golang 写起来更容易一点。

                      1. 桃子

                        谬赞谬赞🥰🥰!

                        我一直恐惧:

                        1. NodeJS。因为 node_modules,所以我最近渲染 KaTex 的时候都换成了 QuickJS
                        2. 以及 Python。因为 dist-packages 经常搞挂我的系统

                        它们多次因为版本问题使我非常崩溃😇😇😇。

                        我这个博客程序确实写得简单,也算是我写它的理念吧。复杂的前端咱我也写不来,我力求把安全、备份之类的做好一点吧。

                        1. 雨帆 https://yufan.me

                          快来和我一起用 Astro。

                          1. 桃子

                            这次该我说逃了……🤪🤪🤪

                            (🏃逃……

                            1. 雨帆 https://yufan.me

                              测试动画?很好玩。

                              1. Dogxxx

                                还能这样?🚴‍♀️🚴‍♂️🚗🚓🚕🛺🚙🚌🚐🚎🚑🚒🚚🚛🚜🚄

                                1. 桃子

                                  漏网之鱼🐟!博客是禁止访客写任何 HTML 的,哪知前几天改崩了逻辑🥵🥵🥵……

                                  已经增加了端到端测试,没得玩儿了!

                2. Dabenshi https://dabenshi.cn

                  值得学习一下,我也是后期加的 Gravatar 服务,本地化确实是很好的解决方案。

                  1. 桃子

                    你网站好像打不开了:

                    504 Gateway Time-out
                    openresty

                    1
                    
                    {"error":"bad gateway: net/http: timeout awaiting response headers"}
                    
                    1. Dabenshi https://dabenshi.cn

                      偶尔cdn抽风,刷新一下就好了

                  2. Dogxxx

                    巧了不是,前天刚写了 正向代理 Gravatar 并生成私有哈希以替代暴露原始哈希的头像链接

                    然后今天给默认头像做了个 dogface

                    样式1 样式2 样式3

                    没测试好就上线了,差点运行不起来🥵

                    1. Dogxxx

                      奇怪,三张图片,为什么只有第一张有 lazy loading,后面两张没有呢;以及后两张都有 title,而第一张没有

                      1. 桃子

                        你真细腻……排查了好一会儿,解决了,墨菲定律还是靠谱🥵。

                        原因是遍历语法树🌲的时候替换/移除了节点(坏习惯!),导致遍历函数提前退出,未完成全部节点的自定义渲染。

                        不得不说,这3️⃣个狗子🐶🐶🐶排在一起看起来真的非常优雅……

                      2. 桃子

                        不是,你这个文章怎么还“藏起来”了呢……竟然在首页都看不到的……🥵

                        刚才看到 Freya 在你文章里面的回复有了头像,以为是他/她看到了我这篇文章后给自己设了个头像…… 原来是你给弄的默认头像🥵。

                        你真的很有才……还会画画🧑‍🎨的……回想我的“关于”,感觉有点羞愧😳。

                        没事,我都是线上风风火火改BUG的,哈哈哈哈😂。想到刚发生的“淘宝首页证书过期”事件,感觉没有比更草台的班子了。不屑😕。

                        1. Dogxxx

                          其实来源是 这个 我只加了一小部分自己画的,然后用 Java 实现了而已。

                          至于藏起来…因为那是 wiki 😬

                          1. 桃子

                            起因是不想整一堆偏技术类的文章在博客上,毕竟我博客的定位还是日常生活。

                            那我正好跟你作对🥵……看来我真的要开始写旅行日记了✈️。

                            其实说实话,我的博客在 Feedly 上我记得有 100+ 订阅,而且肯定是因为技术文章才订阅。我其实有点儿担心写非技术文章污染别人的时间线的🥵。我是不是太……

                            算了,love me,love my dog,🐶。有时候我就是过于顾及别人的感受而忽略自己了。

                            1. Dogxxx

                              如果有了读者基础,那确实需要好好斟酌,是否需要维护相应的关系。幸运又不幸的是:我没有读者基础,所以更能肆无忌惮一些。

                              好好取悦自己,做不了洞那头的大和抚子,做一根坚硬的牛子总可以吧。1

                              1. 桃子

                                好的,宝🥵,学会自爱(literally)。

                      还没有用户发表过评论,我要发表评论
                      编辑评论