中国空气质量在线监测分析平台是一个收录全国各大城市天气数据的网站,包括温度、湿度、PM 2.5、AQI 等数据,链接为:https://www.aqistudy.cn/html/city_detail.html
在分析页面的时候我们发现页面中的数据是动态加载的,当我们点击搜索按钮的时候,就会获取到alax请求到的数据
打开我们动态获取到的数据之后,发现当前ajax请求为post类型的请求,携带一个请求参数d,并且请求参数为加密后的参数并且响应数据也是经过加密后的密文数据
请求的数据
响应的数据
我们已经知道,刚才我们捕获到的ajax请求是通过点击了设定"范围"后的查询按钮后触发的,也就是说该查询按钮上一定绑定了某个点击事件且触发了对应ajax请求发送的事件。那么接下来我们就可以通过火狐浏览器去检测该查询按钮上到底绑定了哪些事件且是否发起了ajax请求,火狐浏览器可以分析页面某些元素的绑定事件以及定位到具体的代码在哪一行
进到点击事件对应的页面源码中,发现真的对搜索按钮添加了一个点击事件。
接下来,我们需要分析getData函数的内部实现,当前源文件中,搜索该方法进行定位,定位到了之后通过分析,发现其内部是调用了下面的这两个方法进行数据的请求
接着分析这两个方法内部的实现,这两个方法就是在getDate实现的下方。再进一步分析发现这两个方法都调用的getServerDate(),这个方法,并传递了method,param等参数,然后还有一个回调函数跟明显是对返回数据进行处理的,这说明ajax请求就是由这和getServerDate()方法发起
定位getServerData方法,查看内部的具体实现。在谷歌浏览器中开启抓包工具,然后对该网站首页发起请求,捕获所有的数据包,然后在所有的数据包中实现全局搜索,搜索该方法是存在在哪个文件中的
JavaScript混淆:我们会惊讶的发现getServerData后面跟着的东西我们看不懂,并且也不符合js函数的写法,其实这里是经过javascript混淆加密了,混淆加密之后,代码将变为不可读的形式,但是功能是完全一致的,这是一种常见的Javascript加密手段。我们想要查看到该方法的实现则必须进行反混淆。
最简单的方法便是搜索在线反混淆网站。http://www.bm8.com.cn/jsConfusion/
在反混淆后,我们很清晰的看到了ajax请求发送的实现,然后还看到了ajax对应post请求的动态加密请求参数的加密方法getParam(),并且将method和object作为了函数的参数。metod和object是从getServerDate函数的参数中获取的。那么getServerData函数中的method和object表示的是什么,我们需要返回去查看getServerDate函数的调用:
发现method是固定形式字符串,object就是param的一个字典,里面存储了三组键值对city表示查询城市名称,startTime和endTime为查询起止事件,typebioassay为HOUR;
至此getParam()函数中两个参数的表示含有我们已经清楚了。getParam函数的返回值就是ajax对应post请求的动态加密请求参数了,我们需要定位到其函数内部的实现,看看如何对请求参数加密的。在getServerDate中我们发现ajax请求对应的操作代码,其中还有一个非常重要的一部,就是ajax请求成功后的回调函数实现内部,接受到了响应数据data,data我们知道是一组密文数据,然后调用了decodeData对data进行解密操作
在反混淆网站的代码中我们搜索到getPrame和decodeDate这两个函数的实现:
服务器响应回来的密文数据是被decodeData进行解密的。观察解密函数发现是通过base64+AES+DES进行的解密!
接下来,我们需要借助于PyExecJS库来实现模拟JavaScript代码执行获取动态加密的请求参数,然后再将加密的响应数据带入decodeData进行解密即可!
开始执行js:
- 将反混淆网站中的代码粘贴到jsCod.js文件中
- 在该js文件中添加一个自定义函数getPostParamCode,该函数是为了获取且返回post请求的动态加密参数:
function getPostParamCode(method, city, type, startTime, endTime){
var param = {};
param.city = city;
param.type = type;
param.startTime = startTime;
param.endTime = endTime;
return getParam(method, param);
}
在py源文件中可以基于pyExecJS模拟执行步骤2中定义好的自定义函数,获取动态加密参数:
import execjs
node = execjs.get()
# Params
method = 'GETCITYWEATHER'
city = '北京'
type = 'HOUR'
start_time = '2018-01-25 00:00:00'
end_time = '2018-01-25 23:00:00'
# Compile javascript
file = 'jsCode.js'
ctx = node.compile(open(file,encoding='utf-8').read())
# Get params
js = 'getPostParamCode("{0}", "{1}", "{2}", "{3}", "{4}")'.format(method, city, type, start_time, end_time)
params = ctx.eval(js)
print(params)
接下来我们用requests库来模拟POST请求
import execjs
import requests
node = execjs.get()
# Params
method = 'GETCITYWEATHER'
city = '北京'
type = 'HOUR'
start_time = '2018-01-25 00:00:00'
end_time = '2018-01-25 23:00:00'
# Compile javascript
file = 'jsCode.js'
ctx = node.compile(open(file).read())
# Get params
js = 'getPostParamCode("{0}", "{1}", "{2}", "{3}", "{4}")'.format(method, city, type, start_time, end_time)
params = ctx.eval(js)
#发起post请求
url = 'https://www.aqistudy.cn/apinew/aqistudyapi.php'
response_text = requests.post(url, data={'d': params}).text
print(response_text)
接下来我们再调用一下 JavaScript 中的 decodeData() 方法即可实现解密:
import execjs
import requests
node = execjs.get()
# Params
method = 'GETCITYWEATHER'
city = '北京'
type = 'HOUR'
start_time = '2018-01-25 00:00:00'
end_time = '2018-01-25 23:00:00'
# Compile javascript
file = 'jsCode.js'
ctx = node.compile(open(file).read())
# Get params
js = 'getPostParamCode("{0}", "{1}", "{2}", "{3}", "{4}")'.format(method, city, type, start_time, end_time)
params = ctx.eval(js)
#发起post请求
url = 'https://www.aqistudy.cn/apinew/aqistudyapi.php'
response_text = requests.post(url, data={'d': params}).text
#对加密的响应数据进行解密
js = 'decodeData("{0}")'.format(response_text)
decrypted_data = ctx.eval(js)
print(decrypted_data)