关于vue中间代理和ASE,RSA解密从而实现对网易云api的伪造请求

0x01 实现步骤概述

技术栈:vue + node+es6+stylus
其中包含的库与模块:axioscrypto-jsrequestrouter
  • vue框架下前端页面编写
  • 本地axios网络请求,server端请求转发
  • 对网易云api加密进行分析,伪造,最后获取信息
  • 整体优化,防止请求错误导致server异常退出

0x02 vue框架下前端页面编写

1.目录结构
image.png

目录结构是非常简单的,组件也非常少,一共有两个组件,

  • mHeader.vue组件是一个头组件,可以在这个组件放入logo或者一些标识,我这里放入的是一个纯色div
  • Search.vue组件是搜索组件,在这个组件内进行歌曲、歌手等搜索,也是我们着重编辑的部分
2.Router

为了以后添加更多的页面组件,这里我们采取router路由的方式来规划页面及页面间的跳转

export default new Router({
  routes: [
    {
      path: '/Search',
      name: 'Search',
      component: Search//引用Search页面
    },
    {
      path:'/',
      redirect:'/Search' //重定向,跳转到Search页面
    }
  ]
})
3.核心组件Search.vue
image.png

页面很简单,主要是一个input框。现在我们希望在键入内容时,在下方弹出提示内容,像这样:


image.png

所以我们为input框绑定事件,方法有很多种,vue下可以便捷的使用这种方式

<input @input="inputFun" placeholder="搜索感兴趣的内容" type="text">

其中inputFun就是我们绑定的事件,每当input内容发生变化,就会执行这个函数。我们在methods中实现这个方法。
函数的具体实现:

methods:{
        inputFun(e){
          this.searchConent = e.target.value  //取出input内容
          console.log(this.searchConent)
          if(this.searchConent.length < 1){  //判断是否为空
            this.info_flag = false           //取消提示框的显示
            return
          }
          //执行查询
          this.searchSubmit(this.searchConent, () => {  //此时input不为空,执行查询函数,callback回调
            if (this.searchConent.length < 1){
              this.info_flag = false
              return
            }else{
              this.info_flag = true
            }
          })
        },
    ...//其他方法
  }

searchSubmit函数执行api查询,以下是searchSubmit函数注意这里需要使用callback回调,不然会出现问题,不使用callback回掉会造成提示框出现过早,我们希望请求到数据以后再进行显示

searchSubmit(data, callback) {
          search(data).then((res) => {
            if(res.data.code == "200"){
              this.searchInfoJson = res.data.result
              callback()
            }else{
              console.log('error:error')
            }
          })
        }

以上search.vue组件就算是写完了

0x03 本地axios网络请求,server端请求转发

1. server中间转发配置

vue在开发环境中调试时,本质就是启动了node服务器,再用这个node服务器去启动vue资源,那么我们的api请求代理转发就可以写在这个默认的node服务器,即在webpack.dev.conf.js中配置即可


image.png

这里我们需要在起始位置引入两个模块

//引入request模块
const request = require('request')

//引入APIenc加密模块
const enc = require('../API_ENC/enc.js')

其中request模块是一个http请求模块,使用它可以轻松的完成GET、POST等请求,我们用它来向网易云音乐获取数据。
enc模块是我们自己编写的加密模块,网易云api请求对数据进行了ASE、RSA加密,我们编写模块来重构数据加密。
devServer节点中建立before(app){},在这里面写http的get和post请求,并通过api的形式传回前端:

devServer: {
    //此处设置代理API
    before(app) {
      app.get('/api/test', (req, res) => {
        console.log(req.query.data)
        console.log('{"s":"'+req.query.data+'","limit":"8","csrf_token":""}')
        const h = enc.enc('{"s":"'+req.query.data+'","limit":"8","csrf_token":""}')
        const _data = 'params='+h.encText+'&encSecKey='+h.encSecKey
        //const _data='params=I9poLQX4QhYUqTlGJ0BuBqrBGfjgpwEOy91ZkftCJVKEh2fEs0EMzJOgYGDTmEyz4GAwdhdAeZ3L0oQU%2BCcmJEBODxiqBinxplaKGtUpfp8%3D&encSecKey=cc402fbec71e6483371fdfc6f7e14701f54b8d0b731617803436647fa1ca8db8e77236287d4be8b21336f04d527e10a7948b6da773d3a5de638b0005a194fc6c48fa6e5de32dcf891c388feec4c97ec4c6b3b6bd208c1389d6776d1cbc16425c9e15847bdb42257390030a5b2660ab6d1db81200d4458f9f6d9e6640b7393f16'
        console.log(_data)
        try{
          request.post({
            url:'https://music.163.com/weapi/search/suggest/web?csrf_token=',
            form:_data,
            headers:{
              'Content-Type':'application/x-www-form-urlencoded',
              'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36'
            }
          }, (error, response) => {
            if(response.body && response.body.length > 1){
              console.log(response.body)
              try{
                res.json({
                  error:0,
                  data:JSON.parse(response.body)
                })
              }catch (e) {
                console.log(e)
              }
            }else{
              console.log('数据空')
              res.json({
                error:0,
                data:{}
              })
            }
            //console.log(response.body)
          })
        }catch (e) {

        }
      }),
      app.post('/api/post', (req, res) => {
        res.json({
          errno: 0,
          data: 'helloPost'
        })
      })
    },
    //设置完毕
...其他代码  
}

其中的enc模块就是我们抽象的网易云音乐加密模块,在后面我们将详细着重介绍这个模块的编写

image.png

另外需要注意的是请求中的headers需要进行配置,否则将不能获取到数据,设置user-agent和content-type是爬虫和仿造请求的两个重要手段,在以后很多情况都要用到。

2. 前端axios请求api配置

当然还需要在前端配置api,其中我们使用了axios模块实现,将axios的get请求封装成search函数,并通过export进行暴露。

import axios from 'axios'

export function search (data) {
  const url = './api/test'
  console.log(data)
  return axios.get(url, {
    params: {
      data:data
    }
  }).then((res) => {
    console.log(res.data)
    return res.data
  })
}

之后,我们便可以在任何组件中轻松的使用这个api,
在search.vue组件中引入

import {search} from '../../api/httpReq'

0x04 对网易云api加密进行分析,伪造,最后获取信息

首先我们对网易云搜索提示的api进行分析,在输入内容前按下F12并转换到Network保持网络抓包开启

image.png

我们可以看到数据很多,点开最后一个,可以在preview中看到数据内容
image.png

image.png

Headers可以看到提交的请求数据
image.png

请求结构非常简单,但是data部分用到了加密,既然要伪造,那么就需要模拟加密过程,这里推荐大家使用fiddler,fiddler可以进行全局搜索,方便我们逆向加密算法,现在转到fiddler,刷新网易云页面,全局搜索encSecKey
image.png

包含关键字的包将会被高亮显示
image.png

这个包是很可疑的,这是一个js库,里面很可能就是加密算法,现在转到浏览器F12中的Source选项中打开这个js库
image.png

这个按钮优化代码显示
image.png

定位到包含encSecKey的位置
image.png

很明显,这个就是加密方法了,现在的主要问题是我们如何去提取这个方法到我们自己的模块。其实很简单,只要把加密过程走一遍分析他是什么类型的加密,这样我们再重构加密方法就易如反掌了。我们下一个断点看看具体参数。
image.png

我们在12861行也就是函数头部下一个断点
image.png

断下来以后我们看到参数d就是被加密的原始数据,而剩下3个参数都是固定值,不发生变化,可以视之为盐。
image.png

这三个按钮分别是步过,步入,和执行到返回,善用调试,可以让问题变得简单。
我们一步一步跟下去,发现执行的顺序是
image.png

所以只要模拟a,b,c这三个函数即可

function a(a) {//主要是取随机生成盐
        var d, e, b = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", c = "";
        for (d = 0; a > d; d += 1)
            e = Math.random() * b.length,
            e = Math.floor(e),
            c += b.charAt(e);
        return c
    }
    function b(a, b) {//数据进行AES加密
        var c = CryptoJS.enc.Utf8.parse(b)
          , d = CryptoJS.enc.Utf8.parse("0102030405060708")
          , e = CryptoJS.enc.Utf8.parse(a)
          , f = CryptoJS.AES.encrypt(e, c, {
            iv: d,
            mode: CryptoJS.mode.CBC
        });
        return f.toString()
    }
    function c(a, b, c) {//盐进行RSA加密
        var d, e;
        return setMaxDigits(131),
        d = new RSAKeyPair(b,"",c),
        e = encryptedString(d, a)
    }
    function d(d, e, f, g) {
        var h = {}
          , i = a(16);
        return h.encText = b(d, g),
        h.encText = b(h.encText, i),
        h.encSecKey = c(i, e, f),
        h
    }

还原b方法可以直接用,不过需要注意引用Crypto-js库,这是一个专门的AES加密解密库。
而还原c方法则不可以直接复制了,因为这里的c方法并不是正常的RSA加密,详细的不同地方同学们可以深入调试,可以重新自己写c方法的加密,在这里不做过多介绍了,除了重写加密方法外,其实还有一种简单的方法,就是根据c函数的调用,找出对应得方法而依次调用,我这里也是采取了这种方法,直接从第12412行第12834行全部粘贴到我们的模块,经过测试这种方式是完全正确的。
所以整个enc模块看起来是这样的:

const CryptoJS = require('crypto-js')
function RSAKeyPair(a, b, c) {
  this.e = biFromHex(a),
    this.d = biFromHex(b),

//......此处省略复制的代码

lowBitMasks = new Array(0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535);
console.log('LOADING--------------------------------------------------------------------------')
function c(a, b, c) {
  var d, e;
  return setMaxDigits(131),
    d = new RSAKeyPair(b,"",c),
    e = encryptedString(d, a)
}
function a(a) {
  var d, e, b = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", c = "";
  for (d = 0; a > d; d += 1)
    e = Math.random() * b.length,
      e = Math.floor(e),
      c += b.charAt(e);
  return c
}
function b(a, b) {
    var c = CryptoJS.enc.Utf8.parse(b),
    d = CryptoJS.enc.Utf8.parse("0102030405060708"),
    e = CryptoJS.enc.Utf8.parse(a),
    f = CryptoJS.AES.encrypt(e, c, {
        iv: d,
        mode: CryptoJS.mode.CBC
    });
    return f.toString()
}
function enc(data){
  var h = {}
  var i = a(16)
  h.encText = b(data, '0CoJUm6Qyw8W8jud')
  h.encText = b(h.encText, i)
  var e = '010001'
  var f = '00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7'
  h.encSecKey = c(i, e, f);
  //console .log('encText:' + h.encText + '\n' + 'encSecKey:' + h.encSecKey)
  return h
}
/*
test:
APIenc('{"s":"周杰伦","limit":"8","csrf_token":""}')
*/
//console.log(enc('{"s":"周杰伦","limit":"8","csrf_token":""}'))
exports.enc = enc

到此,网易云api的加密分析就完成了,enc模块的编写也全部完成。

0x05 整体优化,防止请求错误导致server异常退出

1. server部分优化

在我测试的时候,会经常发生server异常崩溃死掉的情况,主要原因是请求的数据返回空,而我们想要将之解析为json格式,那么将会抛出一个解析格式不正确的错误。所以需要加之判断,判断请求数据是否为空,为空则返回空数据,不为空则进行解析。

if(response.body && response.body.length > 1){//数据不为空
              console.log(response.body)
              try{//尝试进行解析
                res.json({
                  error:0,
                  data:JSON.parse(response.body)
                })
              }catch (e) {
                console.log(e)
              }
            }else{
              console.log('数据空')
              res.json({
                error:0,
                data:{}
              })
            }
2. 前端优化

前端我们创建了一个info_flag标志位来规定是否对提示框进行显示,然而info_flag标志位的true false切换时机变得尤为重要,为了更好的用户体验,我们希望在生成数据以后进行提示框的现实,所以这里就用到了callback回调,在请求到数据以后进行回调,此时再进行标志位的切换。

searchSubmit(data, callback) {//获取数据函数
          search(data).then((res) => {
            if(res.data.code == "200"){
              this.searchInfoJson = res.data.result
              callback()//获取到数据以后再执行callback函数来显示info框
            }else{
              console.log('error:error')
            }
          })
        }

至此,所有的前端和server中间代理就完成了

cnpm run dev //使用此语句来运行吧!

0x06 效果演示

搜索演示.gif

点击github给个星星吧

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容