【爬虫系列】0. 无内鬼,破解前端JS参数签名
PS:这是一个系列,坐等我慢慢填坑。
PS:不太会直接能跑的代码,抛砖引玉。
PS:那些我也不太熟练的就不搞了,包括(破滑块、验证码..)
PS: 反编译搞Apk会有很长的几个文章,稍后慢慢更。
最近,和某XX单位的网站gang上了。
他们家的网页只允许在微信客户端打开,抓包就跟蛋疼了。
不过,手上有Root后的Google Nexus5X,也有 whistle 跨平台抓包工具,
这个倒没太折腾,抓包工具证书往手机系统根证书一扔,完事。
安卓7.0及以上用户证书导入的问题 - entr0py - 博客园
抓到了包,下面蛋疼的事情开始了。
前言: body 加密
嗯?请求Body是这一串东西?
嗯?时隔三年,神奇海螺又出现了?
// json
{
"encryKey": "14a625eb2ec957f9b53412b01de37044e7e2aa6b4b911111c75091cba2a0315b",
"data": "44bc0dab8db8017603586f40554742d14a0c23dd009e35cae5b5ac87dbf7962a311fae30070763d2b48b564d72191fd07a881ebcccfb7c0fdd33e4067bc5119cee5e2fa5eaac10da995c86c8a092dcc3",
"sign": "cc3f924bbb6d57a15bd3e130230f51e55a04fa9e459d177440fbd10bce4b02d0",
"timestamp": 1627224237000
}
很明显,每个单词我们都知道,每个字母和数值我们也懂。
但是....
除了timestamp我们可以生成,其他的明显是加密后数据和签名。
一点都不高能的预警
先说一下思路:
- 捞出核心JS文件
- 读懂加密过程,捞出关键参数
- 用其他语言实现涉及到的加密函数
- 对比加密结果是否一致,尝试去伪造请求
捞JS
首先这货的微信浏览器的,所以没办法使用浏览器开发者工具。
不过,抓包上面不是搞掂了么?直接从抓包结果看HTML就完事。
乖乖一个个请求看,找到第一个返回HTML的响应体。
于是,找到了这个...
哦, 看到了....
<script src="/umi.a029b1fd.js"></script>
看到这货,本宝宝小心脏有点乱跳了。
访问一看。
害,看起来没的那么简单啊,明显这货是被webpack打包后的JS文件。
先下载回来再说...
umi.a029b1fd.js 下载到本地,一看1.5M。
打开一看,毫无疑问没有格式化...
得嘞,大JS文件格式化,先打开我的Ubuntu机器再说。
哦,VS Code format崩;加大内存,继续崩。
搜了一波,找到了神器 Online JavaScript beautifier
文件扔上去,再下载下来...
完事。
毫无疑问,这就是webpack打包后的东西了。
没得事,全局搜一波上面的参数。
完美,看到这个,是不是答案已经出来了。
看看,每个参数怎么算的都告诉我了,还能做撒?还需要做撒?
于是,我午睡去了。
........
其实,最头疼的东西就在这里了。
这时候,很多人会说,上AST 还原JS嘛。
AST抽象语法树--最基础的javascript重点知识,99%的人根本不了解 - SegmentFault 思否
啧啧啧。
道理是这个道理,不过还有其他的思路吗?
直接写个index.html 引入这个JS也成的啊。
<html>
<body>
<h1>test</h1>
</body>
<script src="./app.js"></script>
</html>
开始解JS
var O = Date.parse(new Date),
Y = Object(h["e"])(!1, 16),
j = Object(h["a"])(JSON.stringify({
data: b,
timestamp: O
}), Y),
P = Object(h["f"])(j, Y);
T = {
encryKey: Object(h["a"])(Y, h["b"]),
data: j,
sign: P,
timestamp: O
}
在代码里面看到了一堆这种 h["a"] h["e"],然后跟着参数(j, Y)。
我们明显知道,这是JavaScript的一个函数调用,h看起来是一个map或者是对象,
这里是在调用它的a方法,传入了(j, Y)
在这里,我们最想知道的就是h["a"]的定义是什么样的,
因为知道定义实现,也就能还原完整代码逻辑。
跟一点代码(VS Code跳转定义功能),我们能看到h是什么?
h = n("jNxd"),
看到这里其实是很头疼的,n是个什么玩意我们完全无从得知。
不过这里也能得到点信息,各种各样的函数或者对象都是绑定在”n“上的,
我们只要拿到n,我们需要的h,h[a], h[b] 都知道是什么了。
怎么拿到n呢? 友情提示,善用debugger。
开始寻找n
刚刚我们已经完整把app.js(umi.a029b1fd.js格式化之后的文档)导入我们的index.html
用浏览器打开看看页面。
页面没什么问题,我们尝试在app.js上面加点debugger吧。
加在哪呢?(目的只有一个,能获取的到n)
....h附近前面可以吗?
浏览器控制台打开,刷新页面,切换到Console页面。
试试这里能不能有n对象。
咦,看起来有戏。
试试 h = n("jNxd")
很好,很好,看起来这里是OK的,
h["a"]也是一个函数,符合我们上面看到的。
点击一下上面h["a"]输出的内容,可以跳转到函数定义。
于是,我们来到了重头戏。
s = (e, t) => {
var n = i.a.enc.Utf8.parse(t),
r = i.a.enc.Utf8.parse(t),
o = i.a.enc.Utf8.parse(e),
a = i.a.AES.encrypt(o, n, {
iv: r,
mode: i.a.mode.CBC,
padding: i.a.pad.Pkcs7
});
return i.a.enc.Hex.stringify(a.ciphertext)
},
u = (e, t) => {
var n = i.a.enc.Utf8.parse(t),
r = i.a.enc.Utf8.parse(t),
o = i.a.enc.Hex.parse(e),
a = i.a.enc.Base64.stringify(o),
s = i.a.AES.decrypt(a, n, {
iv: r,
mode: i.a.mode.CBC,
padding: i.a.pad.Pkcs7
});
return s.toString(i.a.enc.Utf8).toString()
},
c = (e, t) => i.a.enc.Hex.stringify(i.a.HmacSHA256(e, t)),
l = (e, t, n) => {
var r = "",
i = t,
o = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "-", ".", "~", "!", "@", "#", "$", "%", "^", "*", "(", ")", "_", ":", "<", ">", "?"];
e && (i = Math.round(Math.random() * (n - t)) + t);
for (var a = 0; a < i; a += 1) {
var s = Math.round(Math.random() * (o.length - 1));
r += o[s]
}
return r
}
看看,代码都出来了,还需要撒?
今天的教程结束,早点睡....
微微一笑,好像没那么简单。
宝哥微微一笑,发现事情没那么简单。
已知,上面这一堆东西,
要不是 i.a.AES,要不是 HmacSHA256
没什么花样。
那么最大的问题就是,
这个加密过程是怎么搞的。
加密向量是什么?秘钥在哪?
SHA256用的是什么参数?参与加密的数据是怎么拼接的?
PS:还在写....
重头戏上场
回到上面的代码
if (p["d"] && "get" !== u.toLowerCase() && !g) {
var O = Date.parse(new Date),
Y = Object(h["e"])(!1, 16),
j = Object(h["a"])(JSON.stringify({
data: b,
timestamp: O
}), Y),
P = Object(h["f"])(j, Y);
T = {
encryKey: Object(h["a"])(Y, h["b"]),
data: j,
sign: P,
timestamp: O
}
}
这里可以看出每个变量是怎么来的。
encryKey = Object(h["a"])(Y, h["b"]) // 调用了a方法
O= Date.parse(newDate) // 生成了时间戳
Y=Object(h["e"])(!1,16) // 调用了e方法
P=Object(h["f"])(j,Y) 调用了f方法
于是我们执行一下看看。
Y看起来是个随机字符串,j,p看起来都是字母+数字组合起来的字符串。
分别到定义出看看是撒。
h["e"]
l = (e, t, n) => {
var r = "",
i = t,
o = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "-", ".", "~", "!", "@", "#", "$", "%", "^", "*", "(", ")", "_", ":", "<", ">", "?"];
e && (i = Math.round(Math.random() * (n - t)) + t);
for (var a = 0; a < i; a += 1) {
var s = Math.round(Math.random() * (o.length - 1));
r += o[s]
}
return r
}
哦,生成了随机字符串。
h["a"]
// n("jNxd")["a"] encryKey
s = (e, t) => {
var n = i.a.enc.Utf8.parse(t),
r = i.a.enc.Utf8.parse(t),
o = i.a.enc.Utf8.parse(e),
a = i.a.AES.encrypt(o, n, {
iv: r,
mode: i.a.mode.CBC,
padding: i.a.pad.Pkcs7
});
return i.a.enc.Hex.stringify(a.ciphertext)
},
哦, AES.encrypt 加密,使用的是CBC/Pkcs7对齐
h["f"] HmacSHA256
(e, t) => i.a.enc.Hex.stringify(i.a.HmacSHA256(e, t))
h["b"] 直接返回了一个固定的字符串。(毫无疑问是IV向量和加密Key)
看看,没了啊。
核心的加密代码就这点。
var O = Date.parse(new Date),
Y = Object(h["e"])(!1, 16),
j = Object(h["a"])(JSON.stringify({
data: b,
timestamp: O
}), Y),
P = Object(h["f"])(j, Y);
T = {
encryKey: Object(h["a"])(Y, h["b"]),
data: j,
sign: P,
timestamp: O
}
所以重点代码又回到这里了,看懂这里就是所有的逻辑了。
读一下,也就这样。
- 获取当前时间戳 O
- 生成随机字符串 Y
- 把传入的b(body)和时间戳组合到一起,设定IV向量为Y,使用AES 加密
- 把密文 j 和 Y进行SHA256签名
- 最用把Y也用AES 加密,这个时候加密IV向量为h["b"]
换个人话
写死了一个iv向量,随机生成一个16位的key,从iv向量对这个Key加密,
用这个Key作为另一个iv变量对请求体Body加密,
然后把上面一堆东西做一个sha256的签名。
哦,说好的前端参数签名加密。
到这里,其实破解过程已经完成了。
这基本也是我睡醒之后,看了台风吃了晚饭回来之后,
开始抄Python 把上面逻辑实现一波的前置思路了。
这个时候,我们也要知道一些东西。
JS加密库 CryptoJS
Python对应的加密库 pycrypto
最后用Python实现这个完整逻辑还是折腾了好一会的,
也抄了不少别的代码,最后贴一下。
from Crypto.Cipher import AES
import base64
import time
import binascii
class AesEncrypt:
def __init__(self, key, iv):
self.key = key.encode('utf-8')
self.iv = iv.encode('utf-8')
# @staticmethod
def pkcs7padding(self, text):
"""明文使用PKCS7填充 """
bs = 16
length = len(text)
bytes_length = len(text.encode('utf-8'))
padding_size = length if (bytes_length == length) else bytes_length
padding = bs - padding_size % bs
padding_text = chr(padding) * padding
self.coding = chr(padding)
return text + padding_text
def aes_encrypt(self, content):
""" AES加密 """
cipher = AES.new(self.key, AES.MODE_CBC, self.iv)
# 处理明文
content_padding = self.pkcs7padding(content)
# 加密
encrypt_bytes = cipher.encrypt(content_padding.encode('utf-8'))
# 重新编码
result = str(base64.b64encode(encrypt_bytes), encoding='utf-8')
print("加密hex:", str(binascii.hexlify(encrypt_bytes),encoding='utf-8'))
return result
def aes_encrypt_to_hex(self, content):
""" AES加密 """
cipher = AES.new(self.key, AES.MODE_CBC, self.iv)
# 处理明文
content_padding = self.pkcs7padding(content)
# 加密
encrypt_bytes = cipher.encrypt(content_padding.encode('utf-8'))
# 重新编码
# result = str(base64.b64encode(encrypt_bytes), encoding='utf-8')
return str(binascii.hexlify(encrypt_bytes),encoding='utf-8')
def aes_decrypt(self, content):
"""AES解密 """
cipher = AES.new(self.key, AES.MODE_CBC, self.iv)
content = base64.b64decode(content)
text = cipher.decrypt(content).decode('utf-8')
return text.rstrip(self.coding)
if __name__ == '__main__':
key = '123'
iv = '123'
ts = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
p_json = {
"CompanyName": "testmall",
"UserId": "test",
"Password": "grasp@101",
"TimeStamp": "2019-05-05 10:59:26"
}
a = AesEncrypt(key=key, iv=iv)
e = a.aes_encrypt("123")
d = a.aes_decrypt(e)
print("加密:", e)
print("解密:", d)
好了,
真完了,
睡觉睡觉。
【爬虫系列】0. 无内鬼,破解前端JS参数签名的更多相关文章
- Web暴力破解--前端JS表单加密进行爆破
0x01 前言 常见的js实现加密的方式有:md5.base64.shal,写了一个简单的demo作为测试. 0x02 代码 login.html <!DOCTYPE HTML> < ...
- 闲聊——浅谈前端js模块化演变
function时代 前端这几年发展太快了,我学习的速度都跟不上演变的速度了(门派太多了,后台都是大牛公司支撑类似于facebook的react.google的angular,angular的1.0还 ...
- 使用react全家桶制作博客后台管理系统 网站PWA升级 移动端常见问题处理 循序渐进学.Net Core Web Api开发系列【4】:前端访问WebApi [Abp 源码分析]四、模块配置 [Abp 源码分析]三、依赖注入
使用react全家桶制作博客后台管理系统 前面的话 笔者在做一个完整的博客上线项目,包括前台.后台.后端接口和服务器配置.本文将详细介绍使用react全家桶制作的博客后台管理系统 概述 该项目是基 ...
- Scrapy入门到放弃01:开启爬虫2.0时代
前言 Scrapy is coming!! 在写了七篇爬虫基础文章之后,终于写到心心念念的Scrapy了.Scrapy开启了爬虫2.0的时代,让爬虫以一种崭新的形式呈现在开发者面前. 在18年实习的时 ...
- 前端构建大法 Gulp 系列 (一):为什么需要前端构建
系列目录 前端构建大法 Gulp 系列 (一):为什么需要前端构建 前端构建大法 Gulp 系列 (二):为什么选择gulp 前端构建大法 Gulp 系列 (三):gulp的4个API 让你成为gul ...
- 【转载】.NET面试题系列[0] - 写在前面
原文:.NET面试题系列[0] - 写在前面 索引: .NET框架基础知识[1] - .NET框架基础知识(1) http://www.cnblogs.com/haoyifei/p/5643689.h ...
- 上传头像,界面无跳转,php+js
上传头像,界面无跳转的方式很多,我用的是加个iframe那种.下面直接上代码. html: //route 为后端接口//upload/avatar 为上传的头像的保存地址//imgurl=/uplo ...
- python 全栈开发,Day134(爬虫系列之第1章-requests模块)
一.爬虫系列之第1章-requests模块 爬虫简介 概述 近年来,随着网络应用的逐渐扩展和深入,如何高效的获取网上数据成为了无数公司和个人的追求,在大数据时代,谁掌握了更多的数据,谁就可以获得更高的 ...
- 爬虫系列1:python简易爬虫分析
决定写一个小的爬虫系列,本文是第一篇,讲爬虫的基本原理和简易示例. 1.单个网页的简易爬虫 以下爬虫的主要功能是爬取百度贴吧中某一页面的所有图片.代码由主要有两个函数:其中getHtml()通过页面u ...
随机推荐
- 多核片上系统(SoC)架构的嵌入式DSP软件设计
多核片上系统(SoC)架构的嵌入式DSP软件设计 Multicore a System-on-a-Chip (SoC) Architecture SoCs的软件开发涉及到基于最强大的计算模型在各种处理 ...
- 操作系统-Linux命令
一.目录结构 #因为根目录与开机有关,开机过程中仅有根目录会被挂载, 因此根目录下与开机过程有关的目录(以下5个),不能与根目录放到不同的分区去. /etc:配置文件 /dev:所需要的装置文件 /l ...
- 源码简析Spring-Integration执行过程
一,前言 Spring-Integration基于Spring,在应用程序中启用了轻量级消息传递,并支持通过声明式适配器与外部系统集成.这一段官网的介绍,概况了整个Integration的用途.个人感 ...
- mybatis之模糊查询
1.编写接口 List<User> getUserLike(String value); 2.编写映射文件 <select id="getUserLike" re ...
- 重新整理 mysql 基础篇————— 事务隔离级别[四]
前言 简单介绍一下事务隔离的基本 正文 Read Uncommitted(未提交读) 这个就是读未提交.就是说在事务未提交的时候,其他事务也可以读取到未提交的数据. 这里举一个例子,还是前一篇的例子. ...
- Git 高级用法,喜欢就拿去用
如果你觉得 git 很迷惑人,那么这份小抄正是为你准备的! 请注意我有意跳过了 git commit.git pull/push 之类的基本命令,这份小抄的主题是 git 的一些「高级」用法. 导航 ...
- 【题解】 hdu2955 Robberies
有抱负的罗伊·劫匪已经看过很多美国电影,他知道坏人通常会被抓住,经常是因为他们太贪心了.他决定在银行抢劫案中工作一段时间,然后退休后到一所大学从事一份舒适的工作. 题目: 罗伊去几个银行偷盗,他既想多 ...
- 从五大结构体,带你掌握鸿蒙轻内核动态内存Dynamic Memory
摘要:本文带领大家一起剖析了鸿蒙轻内核的动态内存模块的源代码,包含动态内存的结构体.动态内存池初始化.动态内存申请.释放等. 本文分享自华为云社区<鸿蒙轻内核M核源码分析系列九 动态内存Dyna ...
- [源码解析] 深度学习分布式训练框架 horovod (8) --- on spark
[源码解析] 深度学习分布式训练框架 horovod (8) --- on spark 目录 [源码解析] 深度学习分布式训练框架 horovod (8) --- on spark 0x00 摘要 0 ...
- nginx服务跳转
1.什么是页面跳转 将URL信息做改变 将URI信息做改变 完成伪静态配置 2.实现页面跳转的方法 http://nginx.org/en/docs/http/ngx_http_rewrite_mod ...