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