treehollow / treehollow-backend

T大树洞 的Golang后端
https://thuhole.com/
GNU Affero General Public License v3.0
167 stars 46 forks source link

匿名性的讨论与优化 #81

Closed xalanq closed 3 years ago

xalanq commented 3 years ago

经树洞管理员建议与允许,我将邮件往来内容搬至这里让大家参与讨论

问题描述

https://github.com/thuhole/thuhole-go-backend/blob/a2d12eb1d3e1a3ba50981c9e5ba05795a2989cf8/pkg/utils/utils.go#L57-L59

这样 hash 是有很大风险的,原因如下:

  1. 大家的清华邮箱大部分都是 @mails.tsinghua.edu.cn 结尾,能区分的仅有前缀,而前缀的长度往往不超过 10,且是英文小写 26 个字母 + 10个数字 + 1 个横线,排除一些不可能的组合后,总体组合情况非常少
  2. 一旦后端服务器被渗透(无法保证永远不会被渗透、脱裤),所有 hash 值能在相当短的时间内被枚举出来(加这个 salt 根本无济于事,因为所有都一样),因此所有人的账户有非常高的反匿名风险

一种可行方案

用户端有两个操作:

  1. 注册。这一点有必要,不再是直接验证码登录了。用户除了提供邮箱 email 外,必须提供一个字符串 nonce(尽量避免用密码这个词,而且在前端给出提示这不是密码,但要强调重要性,一定不能丢),然后这个 nonce 会在第一次注册时,发往用户邮箱(这个邮件需要从服务端邮箱里删掉,保证 nonce 无法被 root 获得),发送一个验证链接到邮箱里。
  2. 登录。登录必须同时提供 email 和 nonce(防止攻击的话或者再加上验证码),以获得一个 user_token

然后在数据库里存储

  1. user_token:存在内存或者 redis 里的 session 数据,用来获取当前用户的 email_nonce_hash
  2. email_hash:就是现有的那个算法,这个只用来判断用户是否注册过
  3. email_nonce_hash:这个用来作为该邮箱的唯一标识,也就是每条树洞的那个用户 id,这是匿名性的根本保证

理论上这三部分是存在一一对应关系的,并且大小是一致的(如果 user_token 设置过期时间的话可能会小一点),但 email_hash 和 email_nonce_hash 两块数据由于没有 nonce,所以无法确定其对应关系。

遇到的问题以及相应的解决方法:

  1. 识别树洞属于哪个用户:通过 user_token 找出 email_nonce_hash,然后判断树洞发帖人的 email_nonce_hash 与找出来的是否匹配
  2. 一个用户只允许一个 user_token 保持在线:每次用户登录,都需要 email 和 nonce,因此计算出 email_nonce_hash 后,可以找出原来的 user_token 并清空,再生成一个新的 user_token
  3. ban 掉某个 email:只需要 ban 掉 email_nonce_hash 就行
  4. 用户想变更 nonce:用户需要提交旧 nonce 来变更,所有旧 email_nonce_hash 绑定的数据全部迁移到新 email_nonce_hash 上去,当然也可以通过 banned list 判断这个用户是否是被 ban 的
  5. 用户忘记了 nonce:可以翻邮件
  6. 用户把 nonce 的邮件删除了:通知管理员,让管理员将该 email 的 email_hash 删除,也就是让该 email 变为没注册。当然,这样会导致旧的 email_nonce_hash 变成野生的,也是本方法唯一存在的多用户漏洞,但这个漏洞发生的频率我认为不会高,而且管理员在操作的时候必须慎重,比如说要求对方提供一条自己发送的树洞证据(用以获取旧的 email_nonce_hash 并进行进一步处理,但要慎重防止冒名顶替),要求用户保证该邮箱不存在被盗用/出卖等。

最后要我说 6 这种人直接别管好了,就别提供这样的操作,谁没事删邮件啊

当然也可以通过以下方法进行缓解 6 这种情况的发生:所有 user_token 都会定期失效,比如 7 天(不能通过自动登录重制刷新时间,当然这样会损失掉一定用户体验,但是为了保护隐私可以告诉用户为何要这样做,我个人觉得这是可以接受的?),然后在用户每次登录的时候,都发一封 email + nonce 的邮件到用户邮箱,防止误删

另外在更新服务端之前,需要将现有的 email_hash 收集起来,用作通知旧用户,强制更新 nonce

本人没发过树洞,对树洞的交互逻辑与实现方式不太熟悉,所以可能有地方没想全,若有纰漏还需要大家指出来哈hhh

最后,这样做的话可能就没办法配合学校/有关部门的调查了(不过仍然可以通过日志记录 email_nonce_hash 与 ip 的方式配合调查?反正查 ip 是学校查,即使库被扒了别人也不知道这个 ip 是谁吧,除非是学校自己)

via 管理员

我们对安全性的保证,更多依赖于服务器加固,手段包括但不限于honeypot;CDN隐藏IP地址;iptables过滤不必要的流量;SELinux防火墙等。

我们目前能想到的优化包含以下几点:

  1. nonce可以由客户端在本地随机生成,以免被质疑收集用户的常用密码。
  2. 您所说的第6种情况可能被已经被封禁的人利用,用来绕过封禁,因此需要更谨慎地对待。也许可以将发生这种情况的无nonce登录接口标准化,并进行180天/次的限制。
  3. email_hash也许可以不用加盐了。
  4. 如果需要配合有关部门调查,一个简单的方案是email_nonce_hash不用hash而改为AES加密;同时把nonce经过Shamir's Secret Sharing算法分发给n位管理员,然后使用n个非对称密钥加密存储到数据库中。这将可以把查询用户邮箱明文的步骤标准化并自动化公示。

欢迎大家讨论!

thuhole commented 3 years ago

可行的一种生产环境技术方案:

  1. 开源客户端要求用户输入“密码”,密码在客户端经过3次sha256,传输到服务器后,作为nounce使用。
  2. email_nonce_hash定义为AES(email, nounce)。数据库中额外存储AES(nounce, random_key),其中random_key对每个用户随机生成,并经过Shamir's Secret Sharing算法分发给n位管理员,超过一半管理员同意后可以解密用户注册nounce从而解密email。
  3. 注册时,强调提醒用户,密码丢失后无法找回(因为找回一个用户的密码需要超过一半管理员同意并解密全部数据库)。
beuzer commented 3 years ago

可否允许用户自己选择nounce邮件的接收地址?清华邮箱不一定是安全的邮箱。