前言
hello,大家好,我是大赛哥(弟),好久不见,甚是想念。
最近因为有小需求研究了两登录的加密,也成功解密加密的参数,在这里给大家分享一波。
前段时间,有个同学他实验室服务器校园网老是掉,想问问有没有啥断线重连的方法。
当时因为比较忙并没有研究,并且也很久没有搞了,昨天没事的时候研究分析了一下,这个过程可能对有基础的人来说是个小菜一碟,但是对于没了解过的可以体验一波说不定后面就用得着。有时候会了一个其他的再会,也就简单了。
这个内容的范畴属于爬虫中的进阶:JS解密 ,当然现在随着加加壳方式多样多彩,反爬手段也越发高明,很多网站尤其是有商业性质数据网站是真的很难搞。
大部分网站,都需要进行权限认证和管理,有很多页面和操作都需要认证后才能访问,而登录就是认证最关键的步骤。我们想在一个页面上畅通无阻,那大部分都是要用户登录的。登录是很多爬虫程序要解决的第一个问题,也有很多时候也是整个爬虫中最复杂最难的部分,只有搞定登录,我们才能用程序。
上面列举登录的两个情况,第一种情况一般很少出现但是我们学生阶段写的登录就是这样实现登录的,明文不加密,但是这种情况不太安全所以大部分登录或者请求会对一些参数进行一些加密,我们如果要用程序模拟这个登录就需要把各个参数形成过程搞懂模拟生成发送才行。当然,登录其实最棘手的验证码问题由于能力有限没研究过这里不做讲解。大部分网站在错误不高情况是没有验证码的,所以大部分场景还是可以尝试搞一搞的。
校园网需要通过http成功登录后可以才访问互联网,这个登录参数密码是加密的,下面就根据我自己所在环境小分析分享给大家。
分析
前面介绍这么多,咱们直奔主题,开始解析这个问题。
对于校园网,我们连接上它的wifi或者网线,我们处在这个校园网的局域网之中,而网络流量需要成本的,当你访问外界网络时候如果没有获得认证授权那么你是无法访问外界服务的,只有成功登录校园网平台才能访问互联网。
然而,现在登录的方式五花八门,我们第一步要观察登录的情况,大致我分成两种,一个是普通表单登录,还有的就是Ajax动态登录,
怎么区分两者呢?
很简单,登录的时候看看url有没有变化(酷酷的??)。
你看,某校的校园网登录页面登录之后它的url是不变的,所以说这个是Ajax登录的情况。
两者有区别嘛?区别不大的,但是Ajax一般情况可以不使用专业抓包工具,而有些form表单的登录可能涉及到各种重定向、新页面可能浏览器不太好抓对应信息,然后需要借助一些fiddler、wireshark等工具抓包。
首先,我们要打开浏览器的F12,打开network这一项,然后点进去XHR这个小目录,这里面all的话获取内容太多,而少部分数据可能藏在JavaScript中(正常不会)。而doc一般就是主页面了,如果普通form表单的话就要看doc请求了。
点击登录之后你就可以看到各个请求交互的内容了。你会发现在这个网页上有个login,login上面有个getchallenge,首先点开login查看携带的参数。
可以看到这个请求的参数有三个,分别是用户名,密码,和一个不知道的challenge,但是上面有个getchallenge请求,然后一看一下果然有一challenge这个参数,当然如果有其他参数,它可能直接存在页面中,也可能通过加密动态生成,就要自己分析啦,从上面图中可以发现,其实我们就只需要破译这个密码的加密方式就得啦(对数据敏感的人可能都已经猜到它是什么加密了)。
既然知道需要解决哪一个参数,那么一般来说可以从两个方面入手,第一个就是利用浏览器元素定位到登录那个按钮,在全局搜索查看js中哪里用到,可以debug其中的逻辑,但是很多这时这种方案看似从前到后实际上你很难发现一些有用内容,因为你不知道它的参数可能在你填写完就加密好了,所以不推荐这种方式。
直接对着参数进行搜索,里面有username、password、challenge这三个参数你可以直接搜,这里面我就搜索password,看看到底哪里用到了password,包括login等词都可以搜搜。最终我在某个地方看到login的逻辑,这个password应该就是经过createChapPassword 方法实现加密。
我们在这里打一个断点,然后点一下登录,程序成功到达断点,并且此时我们的账号密码都还是明文 ,说明数据都还是未被加密的,从这里就要开始捋一捋逻辑了。
进入查看一看函数,就发现核心内容就在这里。
- var createChapPassword = function(password){
- var id = '';
- var challenge = '';
- var str = '';
- id = Math.round(Math.random()*10000)%256;
- $.ajax({
- type : 'POST',
- url : globalVar.io_url + 'getchallenge',
- dataType : 'json',
- timeout : 5000,
- cache : false,
- async : false,
- success : function(resp){
- if(resp && (resp.reply_code != null) && (resp.reply_code == 0)) challenge = resp.challenge;
- }
- });
- str += String.fromCharCode(id);
- str += password;
- for(i=0;i<challenge.length;i+=2){
- var hex = challenge.substring(i,i+2);
- var dec = parseInt(hex,16);
- str += String.fromCharCode(dec);
- }
- var hash = $.md5(str);
- chappassword = ((id<16) ? "0" : "") + id.toString(16) + hash;
- return {password : chappassword , challenge : challenge};
- };
这里面逻辑给大家解读一下其中逻辑,不会不懂的利用搜索引擎搜索一下就好啦。
首先就是随机数产生的一个id,在其他语言复现时候可以选择一个固定的。
然后Ajax发请求获取一个challenge参数,str先加上id对应Unicode的字符,然后依次加上challenge两两组成16进制数字对应Unicode的字符。
对str进行一次MD5加密,然后拼凑一下返回结果就行啦。所以说,参数的加密逻辑就在这里,我们只需要复现就行啦。
逻辑复现
然后事实是复现的逻辑没那么简单。我在复现的时候老老实实前面都没问题,和浏览器的内容进行比对,然而就是MD5在Python中实现的时候结果和前端的MD5加密内容不一致。
这个问题真的是排查了很久,浪费了很多时间,过程也给大家分享一下。
怎么个情况呢,正常的编程语言要对字符串先进行编码,然后再进行MD5编码,而常规编码方式最熟知的就是utf-8了,并且使用在线加密的网站结果都和Pyhton调库加密结果相同。
然后我再尝试控制台打印字符utf-8编码的结果,用浏览器的console对我编码后字符串进行加密,发现了震惊的一幕!这个结果竟然和控制到的结果一致(33c9那一串)。
这就说明,JQuery这个MD5加密库并没有对字符进行utf-8编码而是采取了其他方式,我们需要找到这个方式在编程语言中实现,经过好几番尝试、查找最终终找到一个编码格式:
ISO-8859-1
这个编码还是很久前学习JavaWeb服务器文件下载出现中文名文件名称异常,对文件重新编码遇到过后面就很少接触,用这个编码替代之后,终于打印出我们想要的结果
- ª124412ðRkhìy’LŒÁZosõ
- b'\xc2\xaa124412\xc3\xb0Rkh\xc3\xacy\xc2\x92L*\x08\xc2\x8c\xc3\x81Zos\xc3\xb5'
- hash 297ad4844ee638891233c9ca65df4d9c
- chappasword aa297ad4844ee638891233c9ca65df4d9c
这就完全通了,将代码封装写好尝试一下,这里我用Python实现,Java也可以都一样的,用了requests模块的session(这个模块自动保持cookie),不过代码用不了,只能和前面前端JavaScript逻辑对比一下。
- import requests
- import hashlib
- import urllib
- from requests import sessions
- # header 请求头,通过浏览器请求抓包查看请求所需要的头信息,其中包括返回数据类型、浏览器等信息
- header={
- 'user-agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36',
- 'x-requested-with':'XMLHttpRequest',
- 'accept':'application/json, text/javascript, */*; q=0.01',
- 'accept-encoding':'gzip, deflate, br',
- 'accept-language':'zh-CN,zh;q=0.9',
- 'connection': 'keep-alive',
- 'Content-Type':'application/x-www-form-urlencoded; charset=UTF-8'
- ,'Host': 'm.njust.edu.cn'
- }
- #数据(携带这部分数据传到后台 账号密码等) 我们访问接口需要携带的参数,其中需要我们变换的就是name和password,讲输入的账号和密码赋值进去
- data={
- 'username':'',
- 'password':'',
- 'challenge':''
- }
- def get_challenge():
- url = 'http://m.njust.edu.cn/portal/index.html'
- req = session.get(url)
- #print(req.text)
- req2 = session.post("http://m.njust.edu.cn/portal_io/getchallenge")
- challenge = req2.json()['challenge']
- return challenge
- def get_str2():
- str2 = chr(id)
- str2 = str2 + password
- for i in range(len(challenge)):
- if i % 2 == 1:
- continue
- hex1 = challenge[i: i + 2]
- dec = int(hex1, 16)
- str2 = str2 + (chr(dec))
- return str2
- def login():
- loginurl='http://m.njust.edu.cn/portal_io/login'
- req3=session.post(loginurl,data=data,headers=header)
- print(req3.text)
- if __name__ == '__main__':
- # 第一次登录获取cookie
- id = 162
- session = requests.session()
- challenge = get_challenge()
- username = '12010xxxxxx49'
- password = "12xxxx2"
- str2 = get_str2()
- hash = hashlib.md5(str2.encode('ISO-8859-1')).hexdigest()
- # 打印加密后的密码 #测试结果,是md5 32位加密
- print('hash',hash)
- chappassword = hex(int(id))[2:] + hash ##前面的0X去掉
- print('chappasword', chappassword)
- data['username'] = username
- data['password'] = chappassword
- data['challenge'] = challenge
- login()
准备发射,本来是没网络的登录一下,网络就来了,看来我们的结果是成功的。
发射成功
总结
这个问题对于老手来说并不复杂,但是对于不少人来说可能是个新奇有意思的事情,当然近年来爬虫这种东西自己小玩玩还好,设计商业或者隐私数据大规模抓取可能会有危险哦,后面有机会在分享一些传统登录方式的页面。
这个小加密分析简单复现却因为编码问题卡了很久,说到底还是基础比较薄弱,对这些加密算法和偏底层的基础东西掌握不牢浪费了很多时间,像很多大佬可能看到一个串他可能就猜到这可能是什么加密,这种数据格式是那种类型的编码……不过还好也通过这个demo要补足一下这个盲点。