校園網(wǎng)斷線重連,用爬蟲來(lái)搞定!
前言
hello,大家好,我是大賽哥(弟),好久不見,甚是想念。
最近因?yàn)橛行⌒枨笱芯苛藘傻卿浀募用埽渤晒饷芗用艿膮?shù),在這里給大家分享一波。
前段時(shí)間,有個(gè)同學(xué)他實(shí)驗(yàn)室服務(wù)器校園網(wǎng)老是掉,想問(wèn)問(wèn)有沒(méi)有啥斷線重連的方法。
當(dāng)時(shí)因?yàn)楸容^忙并沒(méi)有研究,并且也很久沒(méi)有搞了,昨天沒(méi)事的時(shí)候研究分析了一下,這個(gè)過(guò)程可能對(duì)有基礎(chǔ)的人來(lái)說(shuō)是個(gè)小菜一碟,但是對(duì)于沒(méi)了解過(guò)的可以體驗(yàn)一波說(shuō)不定后面就用得著。有時(shí)候會(huì)了一個(gè)其他的再會(huì),也就簡(jiǎn)單了。
這個(gè)內(nèi)容的范疇屬于爬蟲中的進(jìn)階:JS解密 ,當(dāng)然現(xiàn)在隨著加加殼方式多樣多彩,反爬手段也越發(fā)高明,很多網(wǎng)站尤其是有商業(yè)性質(zhì)數(shù)據(jù)網(wǎng)站是真的很難搞。
大部分網(wǎng)站,都需要進(jìn)行權(quán)限認(rèn)證和管理,有很多頁(yè)面和操作都需要認(rèn)證后才能訪問(wèn),而登錄就是認(rèn)證最關(guān)鍵的步驟。我們想在一個(gè)頁(yè)面上暢通無(wú)阻,那大部分都是要用戶登錄的。登錄是很多爬蟲程序要解決的第一個(gè)問(wèn)題,也有很多時(shí)候也是整個(gè)爬蟲中最復(fù)雜最難的部分,只有搞定登錄,我們才能用程序。
上面列舉登錄的兩個(gè)情況,第一種情況一般很少出現(xiàn)但是我們學(xué)生階段寫的登錄就是這樣實(shí)現(xiàn)登錄的,明文不加密,但是這種情況不太安全所以大部分登錄或者請(qǐng)求會(huì)對(duì)一些參數(shù)進(jìn)行一些加密,我們?nèi)绻贸绦蚰M這個(gè)登錄就需要把各個(gè)參數(shù)形成過(guò)程搞懂模擬生成發(fā)送才行。當(dāng)然,登錄其實(shí)最棘手的驗(yàn)證碼問(wèn)題由于能力有限沒(méi)研究過(guò)這里不做講解。大部分網(wǎng)站在錯(cuò)誤不高情況是沒(méi)有驗(yàn)證碼的,所以大部分場(chǎng)景還是可以嘗試搞一搞的。
校園網(wǎng)需要通過(guò)http成功登錄后可以才訪問(wèn)互聯(lián)網(wǎng),這個(gè)登錄參數(shù)密碼是加密的,下面就根據(jù)我自己所在環(huán)境小分析分享給大家。
分析
前面介紹這么多,咱們直奔主題,開始解析這個(gè)問(wèn)題。
對(duì)于校園網(wǎng),我們連接上它的wifi或者網(wǎng)線,我們處在這個(gè)校園網(wǎng)的局域網(wǎng)之中,而網(wǎng)絡(luò)流量需要成本的,當(dāng)你訪問(wèn)外界網(wǎng)絡(luò)時(shí)候如果沒(méi)有獲得認(rèn)證授權(quán)那么你是無(wú)法訪問(wèn)外界服務(wù)的,只有成功登錄校園網(wǎng)平臺(tái)才能訪問(wèn)互聯(lián)網(wǎng)。
然而,現(xiàn)在登錄的方式五花八門,我們第一步要觀察登錄的情況,大致我分成兩種,一個(gè)是普通表單登錄,還有的就是Ajax動(dòng)態(tài)登錄,
怎么區(qū)分兩者呢?
很簡(jiǎn)單,登錄的時(shí)候看看url有沒(méi)有變化(酷酷的??)。
你看,某校的校園網(wǎng)登錄頁(yè)面登錄之后它的url是不變的,所以說(shuō)這個(gè)是Ajax登錄的情況。
兩者有區(qū)別嘛?區(qū)別不大的,但是Ajax一般情況可以不使用專業(yè)抓包工具,而有些form表單的登錄可能涉及到各種重定向、新頁(yè)面可能瀏覽器不太好抓對(duì)應(yīng)信息,然后需要借助一些fiddler、wireshark等工具抓包。
首先,我們要打開瀏覽器的F12,打開network這一項(xiàng),然后點(diǎn)進(jìn)去XHR這個(gè)小目錄,這里面all的話獲取內(nèi)容太多,而少部分?jǐn)?shù)據(jù)可能藏在JavaScript中(正常不會(huì))。而doc一般就是主頁(yè)面了,如果普通form表單的話就要看doc請(qǐng)求了。
點(diǎn)擊登錄之后你就可以看到各個(gè)請(qǐng)求交互的內(nèi)容了。你會(huì)發(fā)現(xiàn)在這個(gè)網(wǎng)頁(yè)上有個(gè)login,login上面有個(gè)getchallenge,首先點(diǎn)開login查看攜帶的參數(shù)。
可以看到這個(gè)請(qǐng)求的參數(shù)有三個(gè),分別是用戶名,密碼,和一個(gè)不知道的challenge,但是上面有個(gè)getchallenge請(qǐng)求,然后一看一下果然有一challenge這個(gè)參數(shù),當(dāng)然如果有其他參數(shù),它可能直接存在頁(yè)面中,也可能通過(guò)加密動(dòng)態(tài)生成,就要自己分析啦,從上面圖中可以發(fā)現(xiàn),其實(shí)我們就只需要破譯這個(gè)密碼的加密方式就得啦(對(duì)數(shù)據(jù)敏感的人可能都已經(jīng)猜到它是什么加密了)。
既然知道需要解決哪一個(gè)參數(shù),那么一般來(lái)說(shuō)可以從兩個(gè)方面入手,第一個(gè)就是利用瀏覽器元素定位到登錄那個(gè)按鈕,在全局搜索查看js中哪里用到,可以debug其中的邏輯,但是很多這時(shí)這種方案看似從前到后實(shí)際上你很難發(fā)現(xiàn)一些有用內(nèi)容,因?yàn)槟悴恢浪膮?shù)可能在你填寫完就加密好了,所以不推薦這種方式。
直接對(duì)著參數(shù)進(jìn)行搜索,里面有username、password、challenge這三個(gè)參數(shù)你可以直接搜,這里面我就搜索password,看看到底哪里用到了password,包括login等詞都可以搜搜。最終我在某個(gè)地方看到login的邏輯,這個(gè)password應(yīng)該就是經(jīng)過(guò)createChapPassword 方法實(shí)現(xiàn)加密。
我們?cè)谶@里打一個(gè)斷點(diǎn),然后點(diǎn)一下登錄,程序成功到達(dá)斷點(diǎn),并且此時(shí)我們的賬號(hào)密碼都還是明文 ,說(shuō)明數(shù)據(jù)都還是未被加密的,從這里就要開始捋一捋邏輯了。
進(jìn)入查看一看函數(shù),就發(fā)現(xiàn)核心內(nèi)容就在這里。
- 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};
- };
這里面邏輯給大家解讀一下其中邏輯,不會(huì)不懂的利用搜索引擎搜索一下就好啦。
首先就是隨機(jī)數(shù)產(chǎn)生的一個(gè)id,在其他語(yǔ)言復(fù)現(xiàn)時(shí)候可以選擇一個(gè)固定的。
然后Ajax發(fā)請(qǐng)求獲取一個(gè)challenge參數(shù),str先加上id對(duì)應(yīng)Unicode的字符,然后依次加上challenge兩兩組成16進(jìn)制數(shù)字對(duì)應(yīng)Unicode的字符。
對(duì)str進(jìn)行一次MD5加密,然后拼湊一下返回結(jié)果就行啦。所以說(shuō),參數(shù)的加密邏輯就在這里,我們只需要復(fù)現(xiàn)就行啦。
邏輯復(fù)現(xiàn)
然后事實(shí)是復(fù)現(xiàn)的邏輯沒(méi)那么簡(jiǎn)單。我在復(fù)現(xiàn)的時(shí)候老老實(shí)實(shí)前面都沒(méi)問(wèn)題,和瀏覽器的內(nèi)容進(jìn)行比對(duì),然而就是MD5在Python中實(shí)現(xiàn)的時(shí)候結(jié)果和前端的MD5加密內(nèi)容不一致。
這個(gè)問(wèn)題真的是排查了很久,浪費(fèi)了很多時(shí)間,過(guò)程也給大家分享一下。
怎么個(gè)情況呢,正常的編程語(yǔ)言要對(duì)字符串先進(jìn)行編碼,然后再進(jìn)行MD5編碼,而常規(guī)編碼方式最熟知的就是utf-8了,并且使用在線加密的網(wǎng)站結(jié)果都和Pyhton調(diào)庫(kù)加密結(jié)果相同。
然后我再嘗試控制臺(tái)打印字符utf-8編碼的結(jié)果,用瀏覽器的console對(duì)我編碼后字符串進(jìn)行加密,發(fā)現(xiàn)了震驚的一幕!這個(gè)結(jié)果竟然和控制到的結(jié)果一致(33c9那一串)。
這就說(shuō)明,JQuery這個(gè)MD5加密庫(kù)并沒(méi)有對(duì)字符進(jìn)行utf-8編碼而是采取了其他方式,我們需要找到這個(gè)方式在編程語(yǔ)言中實(shí)現(xiàn),經(jīng)過(guò)好幾番嘗試、查找最終終找到一個(gè)編碼格式:
ISO-8859-1
這個(gè)編碼還是很久前學(xué)習(xí)JavaWeb服務(wù)器文件下載出現(xiàn)中文名文件名稱異常,對(duì)文件重新編碼遇到過(guò)后面就很少接觸,用這個(gè)編碼替代之后,終于打印出我們想要的結(jié)果
- ª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實(shí)現(xiàn),Java也可以都一樣的,用了requests模塊的session(這個(gè)模塊自動(dòng)保持cookie),不過(guò)代碼用不了,只能和前面前端JavaScript邏輯對(duì)比一下。
- import requests
- import hashlib
- import urllib
- from requests import sessions
- # header 請(qǐng)求頭,通過(guò)瀏覽器請(qǐng)求抓包查看請(qǐng)求所需要的頭信息,其中包括返回?cái)?shù)據(jù)類型、瀏覽器等信息
- 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'
- }
- #數(shù)據(jù)(攜帶這部分?jǐn)?shù)據(jù)傳到后臺(tái) 賬號(hào)密碼等) 我們?cè)L問(wèn)接口需要攜帶的參數(shù),其中需要我們變換的就是name和password,講輸入的賬號(hào)和密碼賦值進(jìn)去
- 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()
- # 打印加密后的密碼 #測(cè)試結(jié)果,是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()
準(zhǔn)備發(fā)射,本來(lái)是沒(méi)網(wǎng)絡(luò)的登錄一下,網(wǎng)絡(luò)就來(lái)了,看來(lái)我們的結(jié)果是成功的。
發(fā)射成功
總結(jié)
這個(gè)問(wèn)題對(duì)于老手來(lái)說(shuō)并不復(fù)雜,但是對(duì)于不少人來(lái)說(shuō)可能是個(gè)新奇有意思的事情,當(dāng)然近年來(lái)爬蟲這種東西自己小玩玩還好,設(shè)計(jì)商業(yè)或者隱私數(shù)據(jù)大規(guī)模抓取可能會(huì)有危險(xiǎn)哦,后面有機(jī)會(huì)在分享一些傳統(tǒng)登錄方式的頁(yè)面。
這個(gè)小加密分析簡(jiǎn)單復(fù)現(xiàn)卻因?yàn)榫幋a問(wèn)題卡了很久,說(shuō)到底還是基礎(chǔ)比較薄弱,對(duì)這些加密算法和偏底層的基礎(chǔ)東西掌握不牢浪費(fèi)了很多時(shí)間,像很多大佬可能看到一個(gè)串他可能就猜到這可能是什么加密,這種數(shù)據(jù)格式是那種類型的編碼……不過(guò)還好也通過(guò)這個(gè)demo要補(bǔ)足一下這個(gè)盲點(diǎn)。