本文讲述了如何使用 Cloudflare Worker 来调用 API ,识别用户注册时使用的密码是否已经泄漏。而 Cloudflare Worker 是一项 Serverless 服务,无需提供服务器,可以很方便的部署代码。
原文地址:https://blog.cloudflare.com/using-cloudflare-workers-to-identify-pwned-passwords/ 原文标题:Using Cloudflare Workers to identify pwned passwords 原文作者:John Graham-Cumming 原文写于:2018/2/26 GMT+8 下午8:04:56
译者:驱蚊器喵#ΦωΦ 翻译水平有限,有不通顺的语句,请见谅。
上周,Troy Hunt 发布出了自己的 Pwned Password(已被泄漏的密码) v2 服务,该服务的 API 由 Cloudflare 使用巧妙的匿名方案 处理和缓存 。
以下的perl
代码可以检查密码是否存在于 Troy 的数据库中,但无需将密码发送给 Troy。相关工作原理的详细信息,请参见上面的博客文章。
1 2 3 4 5 6 7 8 9 use strict;use warnings;use LWP::Simple qw /$ua get/;$ua->agent('Cloudflare Test/0.1' ); use Digest::SHA1 qw /sha1_hex/;uc (sha1_hex($ARGV[0 ]))=~/^(.{5})(.+)/ ;print get("https://api.pwnedpasswords.com/range/$1" )=~/$2/ ?'Pwned' :'Ok' , "\n" ;
用其他语言(例如 JavaScript )实现相同的检查同样容易,这让我意识到,我可以将检查合并到 Cloudflare Worker 中。在 JavaScript 朋友的帮助下,我编写了以下 Worker :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 addEventListener('fetch' , event => { event.respondWith(fetchAndCheckPassword(event.request)) }) async function fetchAndCheckPassword (req ) { if (req.method == "POST" ) { try { const post = await req.formData() const pwd = post.get('password' ) const enc = new TextEncoder("utf-8" ).encode(pwd) let hash = await crypto.subtle.digest("SHA-1" , enc) let hashStr = hex(hash).toUpperCase() const prefix = hashStr.substring(0 , 5 ) const suffix = hashStr.substring(5 ) const pwndpwds = await fetch('https://api.pwnedpasswords.com/range/' + prefix) const t = await pwndpwds.text() const pwnd = t.includes(suffix) let newHdrs = new Headers(req.headers) newHdrs.set('Cf-Password-Pwnd' , pwnd?'YES' :'NO' ) const init = { method: 'POST' , headers: newHdrs, body: post } return await fetch(req.url, init) } catch (err) { return new Response('Internal Error' ) } } return await fetch(req) } function hex (a ) { var h = "" var b = new Uint8Array (a) for (var i = 0 ; i < b.length; i++){ var hi = b[i].toString(16 ) h += hi.length === 1 ?"0" +hi:hi } return h }
这个 Worker 可以用于拦截通过 Cloudflare 网络到 Cloudflare 站点的请求。Worker 会查看 POST 请求,并从中提取出一个名为password
的字段,然后根据 Troy Hunt 的服务对其进行检查。
接着,Worker 会添加一个 HTTP 请求头,Cf-Password-Pwned
,其值YES
或 NO
,取决于是否能在数据库中找到这条密码。
POST 请求将被传递到源站服务器进行处理,并插入额外的标头。例如,可以在注册页面上使用它来检查,用户注册时希望使用的密码是否已存在泄漏的数据库中。而服务器仅需查看返回结果中的标头。
显然,这块代码还没有完全准备好在生产环境上运行(例如,没有对失败的请求进行处理的部分),但它很好地说明了 Cloudflare Worker 的功能,作为 Cloudflare 正常请求处理的一部分,执行对 API 的子请求,并在请求时增加信息的能力。
尝试一下 为了进行测试,我创建了一个简单的页面,该页面将接收到的 HTTP 请求标头返回为文本,我将其部署为’signup(注册)’页面,并将上面的 Worker 代码路由绑定到该页面。
我们来检查一个简单的没有 由 Worker 处理的 GET 请求(注意,Cf-Password-Pwned
头不存在)
1 2 3 4 5 6 7 8 9 10 $ curl https://signup.example.com Host: signup.example.com Connection: Keep-Alive Accept-Encoding: gzip Cf-Ipcountry: US Cf-Ray: 3f329308132f92b8-SJC X-Forwarded-Proto: https Cf-Visitor: {"scheme" :"https" } Accept: */* User-Agent: curl/7.26.0
但是带有密码的 POST 请求结果附带额外的标头。从结果可以明显看出,人们不应该使用12345
这样的密码。
1 2 3 4 5 6 7 8 9 10 11 12 13 $ curl -X POST -d 'password=12345' https://signup.example.com Host: signup.example.com Connection: Keep-Alive Accept-Encoding: gzip Cf-Ipcountry: US Cf-Ray: 3f3294e714a36d42-SJC Content-Length: 130 X-Forwarded-Proto: https Cf-Visitor: {"scheme":"https"} Content-Type: application/x-www-form-urlencoded Accept: */* Cf-Password-Pwnd: YES User-Agent: curl/7.26.0
但目前看来密码kRc4qMwAtexDVZVygPnSt7LP5jPFsUDt
是安全的(译者注:根据标头来看,这个密码没有出现在已泄漏的密码数据库中,但是一旦本文发布后,这个密码就已经出现在网络上,所以也不安全了😂):
1 2 3 4 5 6 7 8 9 10 11 12 13 $ curl -X POST -d 'password=kRc4qMwAtexDVZVygPnSt7LP5jPFsUDt' https://signup.example.com Host: signup.example.com Connection: Keep-Alive Accept-Encoding: gzip Cf-Ipcountry: US Cf-Ray: 3f329675e7f29625-SJC Content-Length: 157 X-Forwarded-Proto: https Cf-Visitor: {"scheme" :"https" } Content-Type: application/x-www-form-urlencoded Accept: */* Cf-Password-Pwnd: NO User-Agent: curl/7.26.0
Cloudflare Workers 的强大功能,来自能够在 Cloudflare 的全球边缘节点上运行针对 Service Workers API 编写标准 JavaScript 的能力。一小片段的代码片段可用于转换、优化请求和响应、调用多个 API 来构建响应、以及与 Cloudflare 的缓存交互。
你可以在开发人员文档 中了解更多内容。