博客园的模拟登录实现以及加密方式浅析
此文起因
有园友私信我探讨关于博客园模拟登录的事,年前也玩了一段时间的 node(详情可以参考我的 node 项目集 https://github.com/hanzichi/funny-node 厚着脸皮求 star),做之前想的可能只是一次简单的 post,尝试下来完成后还是有了不少收获。为了能让后人有个参考,遂成此文。
登录抓包
闲话少说,既然是模拟登录,我们来看看登录过程都发生了什么。
打开登录页面(http://passport.cnblogs.com/user/signin?ReturnUrl=http://passport.cnblogs.com/),填入用户名和密码,点击登录后,我们很容易地抓到了登录包。
先看返回头:
后续的实践中,我用回帖操作来证明已经完成登录。抓取回帖的包,发现回帖操作需要携带一个 key 为 .CNBlogsCookie 的 cookie 识别身份。又发现,只要一次登录后将浏览器中的该 cookie 取出,就能模拟该用户了。我将客户端的 key 为 .CNBlogsCookie 的 cookie 取出,写下如下代码:
superagent .post('http://www.cnblogs.com/mvc/PostComment/Add.aspx') .set("Cookie", ".CNBlogsCookie=yourCookieValue") .send({"blogApp": "xxx"}) .send({"body": "test"}) .send({"postId": xxx}) .end(function (err, sres) { // callback });
居然能回帖,完全不用管其他操作了。不明原因,但是细思极恐,如果你被人盗取了该 cookie,后果你懂的。
要手动从浏览器中复制获取该 cookie 显得有点 low,如何能自动获取该 cookie 值?
再看请求头:
实践发现,有四个值是必须模拟的(已标出),而且全都照抄即可。
最后看 post 的数据:
这是什么玩意?原来是加密后的用户名以及密码数据。接下去简单了解下加密方式(尽管模拟登录并不强依赖于此)。
jsencrypt
博客园的加密方式为 RSA,依赖 jsencrypt 这个库。
这里不详述 RSA 加密方式,详情可以参考阮一峰老师的文章:
- RSA算法原理(一)
- RSA算法原理(二)
jsencrypt 加密是可逆的加密方式,客户端用公钥加密,服务端用私钥解密,每次加密生成的字符串都不一样,但是解密后都一样。用明文发送账户名和密码,如果该包被截获,那么你的密码也将大白于天下,存在着极大的安全隐患,但是客户端用了 jsencrypt 加密,如果被截获,截获的也仅仅只是加密后的字符串,没有私钥解密的话,也无济于事。我们 post 包中的 input1 和 input2 的数据正是在客户端加密后的用户名以及密码。
打开 http://passport.cnblogs.com/user/signin?ReturnUrl=http://passport.cnblogs.com,ctrl+u 查看网页源代码,可以清楚看到 jsencrypt 加密的公钥,账户名密码的加密过程,以及用 ajax 方式登录所需要的数据等。
参考 博客园加密登录--jsencrypt 我写了个简单的基于 jsencrypt 的加密解密 demo https://github.com/hanzichi/funny-node/tree/master/cnblogs-auto-login/jsencrypt-demo。因为解密过程有用到 PHP 中的 openssl,所以记得在 php.ini 文件中打开 openssl,具体操作为找到 extension=php_openssl.dll
这一行,把注释打开(将前面的封号去掉)。
另外,根据已经披露的文献,目前被破解的最长 RSA 密钥是 768 个二进制位。也就是说,长度超过 768 位的密钥,还无法破解(至少没人公开宣布)。因此可以认为,1024 位的 RSA 密钥基本安全,2048 位的密钥极其安全。Online RSA Key Generator 可以参考 http://travistidwell.com/jsencrypt/demo/index.html,我这也备份了一份 https://github.com/hanzichi/funny-node/tree/master/cnblogs-auto-login/key-generator。
编码
进入最后编码阶段。
首先我们需要获取加密后的账户名以及密码,可以抓个包复制下参数,虽然每次加密后的字符串都不一样,但是解密后的结果是一样的,所以如果后期不主动修改用户名和密码,这样做完全没有问题。但是我觉得这样做不优雅,能不能通过用户名和密码,获取加密后的结果?尝试着找了下 node 下的 RSA 模块,无奈可能使用方式不大一样,未果。于是换了个方式,客户端进行加密,将加密后的数据存储到服务端,供 node 调用。
然后就是模拟登录了,VerificationToken 参数可以爬取页面取,也可以直接拿个值赋值。登录成功后保存 cookie 以便下次操作时调用。
详细代码和操作步骤已同步在 Github,欢迎交流探讨。