基于 Serverless 的 Valine 可能并没有那么香

2020-11-14
5分钟阅读时长

提醒:本文最后更新于 1471 天前,文中所描述的信息可能已发生改变,请谨慎使用。

Featured Image

声明:《关于被人恶意冒用身份发送垃圾评论的声明》

Valine 是一款样式精美,部署简单的评论系统, 第一次接触便被它精美的样式,无服务端的特性给吸引了。它最大的特色是基于 LeanCloud 直接在前端进行数据库操作而无需服务端,极大的缩减了部署流程,仅需要在静态页引入 Valine SDK 即可。

👨‍💻‍ 初识 Valine

以下是 Valine 官网提供的快速部署脚本,其中 appIdappKey 是你在 LeanCloud 上创建应用后对应的应用密钥。也正是基于这对密钥,Valine 在内部调用了 LeanCloud SDK 进行数据的获取,最终将数据渲染在 #vcomments 这个 DOM 上。这便是 Valine 的大概原理。

<head>
  ..
  <script src='//unpkg.com/valine/dist/Valine.min.js'></script>
  ...
</head>
<body>
  ...
  <div id="vcomments"></div>
  <script>
    new Valine({
      el: '#vcomments',
      appId: 'Your appId',
      appKey: 'Your appKey'
    })
  </script>
</body>

有同学可能会有疑问了,appIdappKey 都直接写在前端了,那岂不是谁都可以修改数据了?这就需要牵扯到 LeanCloud 的数据安全问题了,官方专门写了篇文档《数据和安全》 来说明这个问题。简单的理解就是针对数据设置用户的读写权限,确保正确的人对数据有且仅有正确的权限来保证数据的安全。

乍听一下,保证用户数据只读的话,感觉还是挺安全的。可事实真的如此么,让我们继续来看看。

🙅‍♂️ Valine 的问题

📖 阅读统计篡改

Valien 1.2.0 增加了文章阅读统计的功能,用户访问页面就会在后台 Counter 表中根据 url 记录访问次数。由于每次访问页面都需要更新数据,所以在权限上必须设置成可写,才能进行后续的字段更新。这样就造成了一个问题,实际上该条数据是可以被更新成任意值的。感兴趣的同学可以打开 https://valine.js.org/visitor.html 官网页面后进入控制台输入以下代码试试。试完了记得把数改回去哈~

const counter = new AV.Query('Counter');
const resp = await counter.equalTo('url', '/visitor.html').find();
resp[0].set('time', -100001).save();
location.reload();

可以看到该页面的访问统计被设置成了 -100000 了。这个问题唯一值得庆幸的是 time 字段的值是 Number 类型的,其它的值都无法插入。如果是字符串类型的话就是一个 XSS 漏洞了。

该问题有一个解决办法,就是不使用次数累加的存储方式。更改为每次访问都存储一条只读的访问记录,读取的时候使用 count() 方法进行统计。这样所有数据都是只读的,就不存在篡改的问题了。这种解决方案唯一的问题就是数据量会比较大,对查询会造成一定压力。当然如果是在基于原数据不变的情况下,只能是增加一层服务端来做修改权限的隔离了。

🧯 XSS 安全

从很早的版本开始就有用户报告了 Valine 的 XSS 问题,社区也在使用各种方法在修复这些问题。包括增加验证码,前端XSS过滤等方式。不过后来作者才明白,前端的一切验证都只能防君子,所以把验证码之类的限制去除了。

现有的逻辑里,前端发布评论的时候会将 Markdown 转换成 HTML 然后走一下前端的一个 XSS 过滤方法最后提交到 LeanCloud 中。从 LeanCloud 中拿到数据之后因为是 HTML 直接插入进行显示即可。很明显,这个流程是存在问题的。只要直接提交的是 HTML 而且拿到 HTML 之后直接进行展示的话,XSS 从根本上是无法根除的。

那有没有根本的解决办法?其实是有的。针对存储型的 XSS 攻击,我们可以使用转义编码进行解决。只要效仿早前 BBCode 的做法,提交到数据库的是 Markdown 内容。前端读取到内容对所有 HTML 进行编码后再进行 Markdown 转换后展示。

function encodeForHTML(str){
  return ('' + str)
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')    
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#x27;')
    .replace(/\//g, '&#x2F;');
};

由于 Serverless 攻击者是可以直达存储阶段,所以数据存储之前的一切防范是无效的,只能在读取展示过程处理。由于所有的 HTML 转义后无法解析,Markdown 相当于我们根据自定义的语法解析成 HTML,保证转换后的 HTML 没有被插入的机会。

不过这个方法存在一个问题,那就是对老数据存在不兼容。因为这相当于修改了存储和展示的规则,而之前一直存储的都是 HTML 内容,修复后之前的数据将无法展示 HTML 样式。而为了能在存储的还是 HTML 情况下规避 XSS 安全问题,唯一的办法就是增加服务端中间层。存储阶段增加一道阀门,将转义阶段提前至存储阶段,保证新老数据的通用。

🖼 隐私泄露

说完了存储的问题,我们再来看看读取的问题。攻击者除了可以直达存储,也可以直达读取,当一个数据库的字段开放了读取权限后,相当于该字段的内容对攻击者是透明的。

在评论数据中,有两个字段是用户比较敏感的数据,分别是 IP 和邮箱。灯大甚至专门写了一篇文章来批判该问题 《请马上停止使用Valine.js评论系统,除非它修复了用户隐私泄露问题》。甚至掘金社区在早期使用 LeanCloud 的时候也暴出过泄露用户手机号的安全问题。

为了规避这个问题,Valine 作者增加了 recordIP 配置用来设置是否允许记录用户 IP。由于是 Serverless,目前能想到的也只是不存储的方式解决了。不过该配置项会存在一个问题,就是该配置项的配置权在网站,隐私的问题是评论者遇到的,也就是说评论者是无权管理自己的隐私的。

除了这个矛盾点之外,还有就是邮箱的问题。邮箱本质上只需要返回 md5 用来获取 Gravatar 头像即可。但是由于无服务端的限制,只能返回原始内容由前端计算。而邮箱我们又需要获取到原始值,方便做评论回复邮件通知功能。所以我们也不能不存储,或者存储 md5 后的值。

该问题的解决方案只能是增加一层服务端,通过服务端过滤敏感信息解决这个问题。

🎊 Waline!

基于以上原因,我们发现只有增加一层服务端中间层才能很好的解决 Valine 的安全问题,所以 Waline 横空出世了!Waline 与 Valine 最大的不同就是增加了服务端中间层,解决 Valine 暴露出来的安全问题。同时基于服务端的特性,提供了邮件通知微信通知评论后台管理、LeanCloud, MySQL, MongoDB, SQLite, PostgreSQL 多存储服务支持等诸多特性。不仅如此,Waline 默认使用 Vercel 部署,实现完全免费部署!

Waline 最初的目标仅仅是为 Valine 增加上服务端中间层。但是由于作者不知为何从 1.4.0 版本开始只推送编译后的文件到 Github 仓库中,源文件停止更新。导致我只能连带前端也实现一遍。当然前端的很多代码和逻辑为了和 Valine 的配置保持一致都有参考 Valine,甚至在名字上,我也是从 Valine 上衍生的,让大家能明白这个项目是 Valine 的衍生版。

📔 后记

Serverless 的概念火了非常多年,但技术没有银弹,我们在看到它的优点的同时,也要正视它所带来的问题。而 Serverless 自己可能也意识到了这个问题,从早期的无服务端慢慢转向了无服务器,更偏向 BaaS 了。不过由于 Valine 没有开放源代码,所以上面说的一些问题和解决方法只能等待作者自己发现这件事了。

Avatar
怡红公子 擅长前端和 Node.js 服务端方向。热爱开源时常在 Github 上活跃,也是博客爱好者,喜欢将所学内容总结成文章分享给他人。

26 评论

tufu9441 Edge97.0 Windows 10
2022-01-28 10:52:15 回复

Waline大法好!原来之前ID为li zheming的评论是有人恶意伪造的,当时还好奇公子为何留下如此详细的个人信息,后知后觉的我今天才回去找到并删除……

泠风寒声 Edge96.0 Windows 10
2021-12-26 08:22:47 回复

所以你为什么不用Waline😶

怡红公子 Chrome96.0 Mac OS 10.15.7
2021-12-26 08:40:37 回复

@泠风寒声: 我用的就是 Waline,只是我的客户端是我自己针对 Hugo 定制的,具体可以看我这篇文章《静态博客如何高性能插入评论》

imgradeone Chrome92.0 Windows 10
2021-08-14 09:18:41 回复

果然博主料到了 Valine 的安全性问题,不过这次是伪造 UA 导致评论区无法加载
https://github.com/advisories/GHSA-p2c4-gxp4-j3xp

怡红公子 Chrome92.0 Mac OS 10.15.7
2021-08-15 03:35:47 回复

@imgradeone , 似乎是六月份反馈的,看了下现在都还没有解决诶

怡红公子 Chrome92.0 Mac OS 10.15.7
2021-08-17 14:35:30 回复

@imgradeone , 唔… 这个看起来可能是代码没写好 Bug 导致的问题

Licardo Safari14.0 Mac OS 10.15.6
2020-11-30 02:13:46 回复

你这人是有病吗? 曹 你 妈 的 , 祝你 全家 平安, 啥 比

怡红公子 Chrome87.0 Mac OS 10.15.6
2020-11-30 02:32:17 回复

@Licardo , 你好,请看一下我这篇博客的声明 https://imnerd.org/spam-statement.html 目前有大量 Valine 用户反馈这个问题,但事情不是我做的。目前没有比较好的手段有效的制止这种行为,如果垃圾评论还在持续的话,这边建议您先关评几天。

Licardo WeChat7.0 iOS 14.2
2020-11-30 02:35:08 回复

@怡红公子 , 不好意思,上面骂你确实不是我做的,我没必要也没有时间去做这种事情,应该是别人冒用我身份来骂你的,给你带来不便了。
但是,我确实要祝你们全家平安,你 妈 天天开心快乐

怡红公子 Chrome87.0 Mac OS 10.15.6
2020-11-30 02:40:37 回复

@Licardo , 如果你看完上述的这些内容还是这个态度的话我无话可说。我已经非常客气的在和你沟通了,对于你被垃圾评论攻击的事情,我也非常难受。但是你谩骂我可以,我自己是无所谓的,不过冷言冷语我的家人我觉得就完全显示不出你作为华科人的素质了。最后,之后你的相关评论我不会进行回复,也祝你天天开心快乐。

Licardo WeChat7.0 iOS 14.2
2020-11-30 02:43:23 回复

@怡红公子 , 我也说了,上面确实不是我说的,我没必要干这个事情,应该是别人攻击你的,如果你看完上述的这些内容还是这个态度的话我无话可说。我已经非常客气的在和你沟通了。
希望你能理解,真的不是我评论你的

怡红公子 Chrome87.0 Mac OS 10.15.6
2020-11-30 02:46:59 回复

@Licardo , 友情提示一下,Waline 评论系统比 Valine 较好的地方在于有服务端,后台记录的 IP 基本是真实可信的,而我根据 IP 是可以知道这些评论是不是同一个人发的,请大家都节省下时间,不要再做这种无聊的事情了。

Colsrch Edge87.0 Windows 10
2020-11-24 11:13:39 回复

有病?

怡红公子 Chrome87.0 Mac OS 10.15.6
2020-11-24 11:18:02 回复

@Colsrch , 你好,事情真的不是我做的,我没必要也没有时间去做这种事情。如果给你造成困扰了,只能给你说声抱歉了。

Colsrch Edge87.0 Windows 10
2020-11-24 11:29:12 回复

@怡红公子 , 抱歉,误会了,目前情况来看是Leancloud被攻击了

怡红公子 Chrome87.0 Mac OS 10.15.6
2020-11-24 11:33:51 回复

@Colsrch , 嗯,谢谢理解了。

高浩轩 Edge87.0 Windows 10
2020-11-24 11:06:51 回复

你什么意思?在我博客里刷了1000多条评论,希望不要再干这种事了watermelon

怡红公子 Chrome87.0 Mac OS 10.15.6
2020-11-24 11:11:48 回复

@高浩轩 , 首先那个不是我干的,我是第一次从你的评论点过去你的博客。然后我看了下你使用的是 Valine 评论系统,昵称邮箱网址都是可以自己定义的,所以应该是有人冒用了我的信息做了这种无聊的事。

Colsrch Edge87.0 Windows 10
2020-11-24 11:14:11 回复

@怡红公子 , 不是你干的你回的这么快

怡红公子 Chrome87.0 Mac OS 10.15.6
2020-11-24 11:16:18 回复

@Colsrch , 我的博客评论设置了微信消息通知,所以回复比较及时。另外如果真是我做的,我有必要这么二的留自己的网站地址么?

高浩轩 Edge87.0 Windows 10
2020-11-24 11:26:27 回复

@怡红公子 , 可能就是误会吧,我也不问了,其实你也没必要把个人信息都挂在Github上的,也避免误解whee

怡红公子 Chrome87.0 Mac OS 10.15.6
2020-11-24 11:29:29 回复

@高浩轩 , 嗯,好的,感谢理解。我的个人信息都是透明的,主要是方便大家能找到我。题外话,因为看你用的是 Valine,建议你可以加一下 Valine-Admin https://github.com/DesertsP/Valine-Admin 或者使用下这篇文章里提到的 Waline 评论系统,会带 Akismet 垃圾评论过滤功能,频繁发送的应该是能过滤成垃圾评论的,你可以试试。

高浩轩 Edge87.0 Windows 10
2020-11-24 11:33:23 回复

@怡红公子 , 谢谢whee

{F.A.N} Edge86.0 Mac OS 11.0.0
2020-11-24 10:34:47 回复

有病啊,在我博客里刷广告.
有你这么玩的么?

怡红公子 Mobile Safari14.0 iOS 14.3
2020-11-24 10:58:27 回复

@{F.A.N} , 谢谢反馈,需要澄清的事是我没有做过这种事情。如果你使用的是不需要登录的评论系统,那么昵称邮箱网址都是可以自己定义的,所以应该是有人冒用了我的信息做了这种无聊的事。

大大的小蜗牛 Chrome87.0 Windows 10
2020-11-21 15:43:24 回复

期待 Waline