当前位置: 首页 > news >正文

记一次用 NodeJs 实现模拟登录的思路

记一次用 NodeJs 实现模拟登录的思路

作者 @zwhu
原文章 @github

工欲善其事,必先利其器。

给自己定下写文章的目标后,就去找了几家博客平台来发布文章;作为一个懒人,不能所有博客文章都手动去各家平台发布,只好通过编写脚本来发布。但是除了Github提供了比较详细的Api外,其他国内的博客平台都没有提供对应的接口,但总有办法的。

下面是我对某家博客平台模拟登录流程的记录(打死我都不会说这家平台是S开头的),个人觉得挺有意思的,也能从中学到不少产品安全设计的思路。

工具

  • Babel

  • Cheerio.js

  • SuperAgent

  • Chrome 浏览器

注:工具只是实现结果的一个手段,并不一定需要掌握这些工具,只要知道它们是干嘛的就行了。

开始分析

先进入主页找到用户登录页,如下图所示:

登录

标准的登录框,在这边需要把Chrome的控制台打开,进入Network页,把 Preserve log (页面跳转也能记录日志,感谢 铁臂狗 告知)的选项勾中, 如下图所示:
Chrome 控制台

抓包分析请求,先从输入正确密码开始:

输入正确密码

正确密码的包

我把暴露隐私的两个地方打码了(这两块也是我们接下来要着重要分析的点)

可以从中看到请求头,我们先把这些请求头照抄下来

const base_headers = {
    Accept: '*/*',
    'Accept-Encoding':'gzip, deflate',
    'Accept-Language':'zh-CN,zh;q=0.8,en;q=0.6,zh-TW;q=0.4,ja;q=0.2',
    'Cache-Control':'no-cache',
    Connection:'keep-alive',
    DNT:1,
    Host:'segmentfault.com',
    Origin: 'http://segmentfault.com',
    Pragma:'no-cache',
    Referer: 'http://segmentfault.com/',
    'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36',
    'X-Requested-With': 'XMLHttpRequest'
    }

排除法删除 Cookie

我们可以看到在请求登录的时候 Header 就已经带有 Cookie 了,这在我平常的设计中没有做过,所以我就试着把 Cookie 删后再请求,看看有什么效果。删除 Cookie 的方法如下所示:

删除Cookie的方法

利用排除法不停删除并继续试着登录,都能完成登录;直到删除 PHPSESSID 的时候发现删除之后再登陆是会报错的,所以这个 PHPSESSID 肯定是有用的(没用过PHP对这个不太了解),因此我断定这个 Cookie 是在后端作为验证登录的一个字段;因此我可以通过在登录之前先下载首页并拿到 Cookie,放到请求头上做作为模拟 Header。

获取 Cookie

import request from 'superagent'

let cookie;

req
.get(urls.mainpage)
.end((err, res) => {
   // 从上图可以看到我们需要的cookie是PHPSESSID开头的
    cookie = res.headers['set-cookie']
                .join(',').match(/(PHPSESSID=.+?);/)[1]
})

获取页面 token

本以为拿到 Cookie 之后就可以开开心心的做登录请求,然而这么简单的话这篇文章页也就没什么写的必要了。

继续分析请求 HTTP 包,可以发现在每次请求的时候,url 后面总是会带一个 queryString(图 2),我在这里耗费了不少时间,毫无头绪,只能追进源码里面摸索。

压缩后的源码

找到上图中的源码,可以看到这个源码是被压缩过的,不要着急,chrome 提供了 formatt 功能,点击最下面的{},可以对压缩的代码重排,至少是勉强可以阅读的代码了。

美化后的代码

接下来的事情就是怎么从这堆代码中抽丝剥茧找到对我们有用的信息,可是这么多的代码一步步看下来也会看到头晕脑胀,眼睛滴血。那么就试试看能不能使用查找的方式从源码中找到我需要的东西。使用快捷键 ctrl+F,键入 /login/login是作为登录的链接的,感觉上可能会有很大概率能搜到相关代码)

搜索代码

很巧的是,搜到了相关的代码。从中可以看到此网站使用了 JQuery 的 Ajax 发送相关 HTTP 请求,那么,url 便是 e.attr("action"),从下面的 DOM 结构能看到 action 是api/user/login

DOM结构

还是没有找到 queryString, 那就换个关键词试试看,这次搜索 _=(看图2,queryString 是由_=拼接起来的)

搜索代码2

从上图可以看到有7个结果,而被黄色标注出来的那行才是我们想要的。JQ 的 ajaxSend 可以在 Ajax 发送之前做一些处理。从上图可以看出,请求的时候在 url 的后面增加一个 n._ ,那就继续去找n._是什么?由于截图截少了,我就不再重新截图,从上图的第一行可以看到 _ 是window.SF.token,由此我们就摸到 token 的 G 点,整个流程明朗了许多。接下来全局搜索 window.SF.token,没找到。我知道 window 是全局变量,为什么把 token 放到 window 上?可以想多的是 token 并没有在当前的 script 标签内。接下来去 index.html 内查找:

token

找到了!可以看到 token 是被包裹在一个独立的 script 标签内,在后端生成HTML模板的时候就已经插入。

找到 token 之后就很简单了,拿到这个字符串表达式,运行,拿到token。
原理我之前写过一篇文章,移步

import cheerio from 'cheerio'
import request from 'superagent'

let cookie;


// 为什么这样做
function getToken(s) {
  let $ = cheerio.load(s)
  , text = $('body script').eq(2).text()
  , fn = new Function('window', text + ';return window.SF.token')
  , token = fn({})

  $ = null
  return token
}


req
.get(urls.mainpage)
.end((err, res) => {
   let token = getToken(res.text)
   
   // 从上图可以看到我们需要的cookie是PHPSESSID开头的
    cookie = res.headers['set-cookie']
                    .join(',')
                    .match(/(PHPSESSID=.+?);/)[1]
})

开始登录吧

拿到 token 和 Cookie ,抓包分析所需要的登录字段:

{
    mail: 'xxxxx@xx.xx', // 邮箱
    password: 'xxxxxxx', // 密码
    remember: '1'  // 是否记住登录
}

登录:

req
.get(urls.mainpage)
.end((err, res) => {
   let token = getToken(res.text)
   
   // 从上图可以看到我们需要的cookie是PHPSESSID开头的
    cookie = res.headers['set-cookie'].join(',')
                .match(/(PHPSESSID=.+?);/)[1]
    
    req
   .post(urls.login)
   .query({'_': token})
   .set(base_headers)
   .set('Cookie', cookie)
   .type('form')
   .send(conf)
   .redirects(0)
   .end((err, res) => {
        console.log(res)
    })
  })
})

总结

世上无难事只怕有心人

登录是最基础也最核心的功能,通过对登录流程的分析,基本弄清楚了此博客平台的验证机制,在分析的过程中斗智斗勇,利用自己掌握的知识一步一步破解谜题的本身就是一件很有意思的事情,以后也可以将此方法用到自己的登录流程设计中。

TODO

登录之后能施展的手段就很多了: 提问题,发表文章,创建标签等等,用到得知识都在上面说过了,按下不表。

有需要源码的同学,欢迎 Star

相关文章:

  • position定位的小问题
  • 分析一下云ERP与本地ERP相比区别在哪里
  • Skia深入分析5——skia文字绘制的实现
  • Linux下Tomcat的安装配置
  • FAT32,NTFS,EXT3,支持的最大分区和单个文件大小?
  • leetcode先刷_Valid Sudoku
  • [译] 怎样写一个基础的编译器
  • 攻城记:Thinkphp框架的项目规划总结和踩坑经验
  • POJ 3421 X-factor Chains(构造)
  • Apache Tez 介绍(译)
  • LinkedList的用法小结
  • 在linux下配置静态IP
  • TotoiseSVN基本用法
  • android studio 无法在可视化页面预览布局文件
  • ubuntu php mysql
  • ABAP的include关键字,Java的import, C的include和C4C ABSL 的import比较
  • Android Volley源码解析
  • httpie使用详解
  • markdown编辑器简评
  • Rancher-k8s加速安装文档
  • React系列之 Redux 架构模式
  • spring boot 整合mybatis 无法输出sql的问题
  • Wamp集成环境 添加PHP的新版本
  • 阿里云购买磁盘后挂载
  • 力扣(LeetCode)21
  • 那些被忽略的 JavaScript 数组方法细节
  • 浅谈web中前端模板引擎的使用
  • 使用权重正则化较少模型过拟合
  • 我的面试准备过程--容器(更新中)
  • 一个6年java程序员的工作感悟,写给还在迷茫的你
  • 异常机制详解
  • 《码出高效》学习笔记与书中错误记录
  • 阿里云服务器如何修改远程端口?
  • ​RecSys 2022 | 面向人岗匹配的双向选择偏好建模
  • (12)Hive调优——count distinct去重优化
  • (4)logging(日志模块)
  • (C语言)二分查找 超详细
  • (c语言版)滑动窗口 给定一个字符串,只包含字母和数字,按要求找出字符串中的最长(连续)子串的长度
  • (MonoGame从入门到放弃-1) MonoGame环境搭建
  • (编程语言界的丐帮 C#).NET MD5 HASH 哈希 加密 与JAVA 互通
  • (力扣记录)1448. 统计二叉树中好节点的数目
  • (六)激光线扫描-三维重建
  • (免费领源码)Java#ssm#MySQL 创意商城03663-计算机毕业设计项目选题推荐
  • (强烈推荐)移动端音视频从零到上手(下)
  • (一)为什么要选择C++
  • (转) ns2/nam与nam实现相关的文件
  • (转载)利用webkit抓取动态网页和链接
  • .NET Core 2.1路线图
  • .Net Core和.Net Standard直观理解
  • .NET/MSBuild 中的发布路径在哪里呢?如何在扩展编译的时候修改发布路径中的文件呢?
  • .netcore 获取appsettings
  • .net连接MySQL的方法
  • .Net通用分页类(存储过程分页版,可以选择页码的显示样式,且有中英选择)
  • @Transient注解
  • [<死锁专题>]