为什么忘记密码时只能重设,不把旧密码告诉我?

某天小明在整理他的收藏夹时发现了一个以前很常逛,但已经将近半年多没去的一个论坛。小明想回去看看那边变得怎么样了,于是点进去那个论坛,输入了帐号密码,得到了密码错误的提示。

尝试了几次之后,系统提示小明可以使用「忘记密码」的功能,所以小明填了自己的 email 之后去收件箱里查看,发现系统传来一个「重设密码」的链接。虽然说最后小明成功利用重新设定的密码登入,但有个问题让他百思不得其解:

奇怪呀,为啥要我重新设置密码,把旧的密码发到邮箱里给我不就好了?

应该有许多人都跟小明一样,有过类似的疑惑。把旧密码发给我不是很好吗,干嘛强迫我换密码?

这一个看似简单的问题,背后其实藏了许多信息安全相关的概念,就让我们慢慢寻找问题的答案,顺便学习一些基本的信息安全知识吧!

被偷走的数据库

大家应该很常看到新闻说哪个网站的数据又被偷走了,顾客个人数据全部都泄露出去。像是国外知名的网域代管网站 GoDaddy[1] 就泄露了 120 万笔用户数据,像国内的微博、淘宝等也有过数据泄露的情况发生。

这边我想带大家探讨的两个问题是:

  1. 数据真的这么容易泄露吗?

  2. 数据泄露之后,可能造成什么后果?

我们先来看第一个问题,有很多安全性的漏洞可以造成数据泄露,而有些漏洞的攻击方式,比你想的还简单一百倍。

你想像中的黑客可能像上面那样,打着一大堆不知道在干嘛的指令,画面上出现很多黑底白字或是绿字的画面,完全搞不懂在干嘛,但是做着做着网站就被攻破下来了。

而事实上有些漏洞,可能在地址栏上面改几个字就攻击成功了,就算你不懂任何代码也做得到。

举例来说好了,假设今天有个购物网站,你买了一些东西之后送出订单,订单成立后跳转到订单页面,上面有着一大堆你的个人数据,例如说:姓名、收货地址、联络电话以及 Email 等等。

然后你发现订单页面的网址是 https://shop.xiaoming.cn/orders?id=14597

而正好你的订单编号也是 14597,在好奇心的驱使之下,你就试着把数字改成 14596,然后按下 Enter。

当网站载入完成之后,你竟然还真的能看到编号为 14596 的订单,上面出现一个你不认识的人的姓名、收货地址、联络电话跟 Email。

有些攻击就是这么朴实无华且枯燥,只要改个字就能看到属于其他人的数据。这时候如果你会写程序的话,就可以写个脚本自动去抓 id 是 1 一直到 id 是 15000 的数据,你就拿到了这个购物网站 15000 笔订单的资讯,也就是一万多个顾客的个人数据。

这过程中没有什么黑底白字的画面,也不用一直疯狂打字,唯一需要的只有改数字,个人数据就轻松到手。

这类型的漏洞有个专有名词,称为 IDOR,全名是:Insecure direct object references,大约就是不安全的直接数据存取的意思。漏洞产生的原因就是工程师在开发时,并没有注意到权限控管,因此让使用者能存取到其他人的数据。

有些人看到这边可能以为我只是为了文章浅显易懂,所以才举一个简化的例子,现实生活中的攻击才没这么简单。

这句话算是对了一半,大部分的网站确实都不会有这么明显的一个漏洞,攻击方式会更复杂一点。但可怕的是,还真的有些网站就是这么简单,就是改个数字就可以拿到别人的数据。

例如说这两个就是 IDOR 的真实漏洞:

  1. 享健身 xarefit 任意访问/下载所有会员个人数据[2]

  2. DoorGods 防疫门神实联制系统 IDOR 导致个人数据泄露[3]

对,不要怀疑,就真的只是在网址上改个数字而已这么容易。

以后只要看到网址列上有这种数字,就可以试着去改改看,搞不好不会写程序的你也可以发现 IDOR 的漏洞。

除了这种只要改个东西的漏洞之外,还有另外一个很常见但是需要一点技术能力才能攻破的漏洞,叫做 SQL Injection。

先来讲讲 SQL 是什么,简单来说就是跟数据库查询东西的一种程序语言。既然说是语言那就会有固定语法,若是以中文举例,大概就像是:

去找「订单数据」,给我「id 是 1 的」,按照「建立时间」排序

用「」框起来的部分代表可以变动,而其他关键字例如说「去找」、「给我」这些都是固定的,因为语法要固定才能写程序去解析。

同样以上面假想的购物网站为例,如果网址是 https://shop.xiaoming.cn/orders?id=14597 ,那网站去跟数据库拿数据时,指令大概就是:

去找「订单数据」,给我「id 是 14597 的」

因为网址列上的 id 是 14597 嘛,所以这个 id 就会被放到查询的指令去,如果 id 是别的,那查询的指令也会不一样。

这时候如果我的 id 不是数字,而是「1 的顺便给我使用者数据」,查询就变成:

去找「订单数据」,给我「id 是 1 的顺便给我使用者数据」

那整个网站的使用者数据就顺便被我抓下来了。

这个攻击之所以叫做 SQL injection,重点就在于那个 injection,攻击者「注入」了一段文字被当作指令的一部分执行,所以攻击者就可以执行任意查询。

比起上面讲的 IDOR,SQL injection 通常会更为致命,因为不只是订单数据本身,连其他数据也会被一起捞出来。所以除了订单数据,会员数据跟商品数据都有可能一起泄露。

这边也随便找两个公开的案例:

  1. 北一女中网站存在 SQL Injection 漏洞[4]

  2. 桃园高中 网站 SQL injection[5]

而防御方式就是不要把使用者输入的「1 的顺便给我使用者数据」直接当作指令,而是经过一些处理,让整段查询变成:「给我 id 是:『1 的顺便给我使用者数据』的数据」,那因为没有这个 id,所以什么事也不会发生。

个人数据泄漏了,然后呢?

前面我们已经看到了针对那些没有做好防御的网站,个人数据泄露是多么容易的一件事情。

那个人数据泄漏之后,对使用者会有什么影响呢?

大家最感同身受的应该就是诈骗电话吧,例如说某些买书的网站或是订房网站,打过来跟你说什么要分期退款,为了博取你的信任,连你买了哪本书,订了哪个房间,或是你家地址跟姓名全都讲得出来。

这些都是因为数据泄露的缘故,诈骗集团才会知道的这么清楚。

但除了这些个人数据以外,还有两个东西也会泄露,那就是你的帐号跟密码。

也许你会想说:「不就帐号跟密码吗,我就在那个网站上面改密码以后再用就好啦!」

事情也许没有你想的这么简单。如果你没有用密码管理软件的话,我大胆猜测你所有的密码可能都是同一个。因为怕记不起来嘛,所以干脆都用同一个密码。

这时候如果账号和密码泄露,黑客是不是就可以拿这个账号和密码去其他服务试试看?

拿去登你的 Google,登你的 Facebook,这时候用同一个密码的人就会被登进去。所以从表面看只是一个购物网站被入侵,但造成的结果却是你的 Google 还有 Facebook 也一起被盗了。

所以,有时候某个网站被盗帐号可能不是那个网站的问题,而是黑客在其他地方拿到了你的帐号密码,就来这边试试看,没想到就中了。

对于网站的开发者而言,保护好使用者的个人数据是天经地义的事情,保护密码也是,有没有什么好方法可以保护密码呢?

加密吗?把密码用某些算法加密,这样数据库储存的就会是加密后的结果,尽管被偷走了,黑客只要没有解密的方法就解不开。

听起来似乎是最安全的做法了,但其实还有一个问题,那就是网站的开发者还是会知道怎么解密,如果有工程师监守自盗怎么办?他还是可以知道每个使用者的密码是什么,可以把这些资讯拿去卖或者是自己利用。

嗯...似乎我们也不能怎么样,因为无论如何,开发者都需要有方法知道数据库存的密码究竟是多少吧?不然在登入的时候怎么确认帐号密码是对的?

再者,这样听起来应该够安全了,要怎么样才能更安全?难道要连网站的开发者都无法解密,都不知道密码是什么才够安全吗?

Bingo!答对了,就是要这样没错!

没有人知道你的密码,包括网站本身

事实上,网站的数据库是不会储存你的密码的。

或更精确地说,不会储存你的「原始密码」,但会储存密码经过某种运算后的结果,而且最重要的是, 这个运算是无法还原的

直接举例比较快,假设今天有个很简单的算法,可以把密码做转换,转换方式是:「数字不做转换,英文字母把 a 换成 1,b 换成 2...z 换成 26」,以此类推,第几个字母就换成几,大小写不分都一样(先假设不会有符号)。

如果密码是 abc123,转换完就变成 123123。

在使用者注册的时候,网站就把使用者输入的 abc123 转成 123123,然后存到数据库里面。因此数据库存的密码是 123123,而不是 abc123。

当使用者登入时,我们就再把输入的值用同样的逻辑转换,如果输入一样,转换后的结果就会一样对吧?就知道密码是不是正确的。

当黑客把数据库偷走以后,会拿到 123123 这组密码,那一样啊,不是可以推论出原本是 abc123 吗?不不不,没这么简单。

123123、abcabc、12cab3...这些密码转换之后,不也是 123123 吗?所以尽管知道转换规则跟结果,却没有办法还原成「唯一一个密码」,这就是这个算法厉害的地方!

这样的转换就叫做哈希(Hash),abc123 每次 hash 过后的结果都会是 123123,但是从 123123 却无法得到输入一定是 abc123,因为有其他种可能性存在。

这就是 hash 跟加密最大的不同。

加密跟解密是成对的,如果可以加密就一定可以解密,所以你知道密文跟密钥,就可以知道明文。但 hash 不同,你知道 hash 算法的结果,却无法逆推出原本的输入是什么。

而这个机制最常见的应用之一,就在于密码的储存。

在注册时把 hash 过后的密码存进数据库,登入时把输入的密码 hash 过后跟数据库比对,就知道密码是否正确。就算数据库被偷,黑客也不知道使用者的密码是什么,因为逆推不出来。

这就是为什么忘记密码的时候,网站不会跟你讲原本的密码是什么,因为网站本身也不知道啊!

所以不能「找回密码」,只能「重设密码」,因为重设就代表你输入新的密码,然后网站把新的密码 hash 之后存进数据库,未来登入时就会用这组新的 hash 去比对。

有些人可能会注意到这样的储存方式似乎有个漏洞,延续前面的例子,数据库存的是 123123 而我的原始密码是 abc123,这样如果用「abcabc」,hash 过后也是 123123,不就也可以登入吗?这样不太对吧,这不是我的密码嘛

有两个不同的输入却产生出同一个输出,这种状况称为碰撞(hash collision),碰撞一定会发生,但如果算法设计的好,碰撞的机率就超级无敌小,小到几乎可以忽略。

前面提的转换规则只是为了方便举例,真实世界中用的算法复杂许多,就算只有一个字不同,结果都会天差地远,以 SHA256 这个算法为例:

  1. abc123 => 6ca13d52ca70c883e0f0bb101e425a89e8624de51db2d2392593af6a84118090

  2. abc124 => cd7011e7a6b27d44ce22a71a4cdfc2c47d5c67e335319ed7f6ae72cc03d7d63f

类似的输入却产生截然不同的输出。

像我前面举例用的转换就是不安全的 hash 算法,要尽量避免使用或是避免自己设计,尽可能使用密码学家跟专家设计出的算法,像是上面提到的 SHA256。

在使用这些算法的时候,也要特别注意一下是否安全,因为有些算法虽然也是由专家设计,但已经被证明是不安全的,例如说密码用 MD5 来 hash 后储存就是不安全的,可以参考:Is MD5 considered insecure?[6]

储存 hash 后的值就没事了吗?

抱歉,其实只储存密码 hash 过后的值是不够的。

咦,为什么?我刚刚不是说没办法反推出结果吗,那为什么不够?

虽然说没办法反推出结果,但攻击者可以利用「输入一样,输出一定一样」的特性,先建好一个人数据料库。

举例来说,假设有个很常见的密码 abc123,hash 过后的值是 6ca13d,那攻击者就可以先算好,然后把这个关系存在数据库,所以攻击者的数据库里面就可能会有一百万组最常见密码的清单,里面有着每个密码跟它 hash 过后的值。

那接下来只要在 hash 过后的数据库发现 6ca13d,攻击者就可以透过查表的方式,查出原本的密码是 abc123。这不是利用算法反推结果,这只是利用现有数据来查询而已。

为了防御这种攻击,还要做一件事情叫做加盐(Salting),没错,就是盐巴的那个盐。通常会帮每个使用者产生一个独一无二的盐,例如说 5ab3od(实际上会更长,可能 16 或 32 个字以上),接着把我的密码 abc123 加上我的盐,变成 abc1235ab3od,然后用这个加盐过后的结果去做 hash。

为什么要这样做呢?

因为攻击者预先准备好的表格中,比起 abc123,出现 abc1235ab3od 的机率显然更低,同时又因为长度变长了,暴力破解的难度变得更高。如此一来,密码就变得更难破解了。

结语

忘记密码时网站不会把密码发给我,因为网站自己都不知道我的密码是什么。虽然听起来不太可能,但实际状况就是如此。为了安全性,这是必须的手段。

要达成这样的目的,背后最重要的技术原理就是 hash,「同样的密码会产生同样的 hash 值,但从 hash 值没办法对应回原本的密码」就是秘诀所在。

反之,如果你发现有网站可以找回你的密码,那就得要多加注意,有可能网站数据库存的不是 hash 值而是你的密码。在这种状况下,万一有天数据库被入侵,账号和密码被偷走,黑客就能得知你真实的密码,然后去试其他的服务。

有关于密码管理,现在浏览器也有功能可以自动帮你产生密码外加记忆密码,或也可以使用现成的密码管理软件,都可以在不同网站产生不同的密码。

这篇希望能让对这个领域陌生的读者们也能知道一些基本的概念,包括:

  1. 有些网站比你想得脆弱很多,改个网址就可以拿到别人的数据

  2. 对于安全性做得不好的网站,拿到整个人数据料库不是一件难事

  3. 忘记密码只能重设,不能找回,是因为网站也不知道你的密码

  4. 如果有网站可以把旧密码给你,那你得要小心一点

相关推荐
FreeCultureBoy2 分钟前
macOS 命令行 原生挂载 webdav 方法
前端
bobz96513 分钟前
ovs patch port 对比 veth pair
后端
Asthenia041223 分钟前
Java受检异常与非受检异常分析
后端
uhakadotcom37 分钟前
快速开始使用 n8n
后端·面试·github
uhakadotcom38 分钟前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom42 分钟前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom44 分钟前
React与Next.js:基础知识及应用场景
前端·面试·github
JavaGuide44 分钟前
公司来的新人用字符串存储日期,被组长怒怼了...
后端·mysql
uhakadotcom1 小时前
Remix 框架:性能与易用性的完美结合
前端·javascript·面试
bobz9651 小时前
qemu 网络使用基础
后端