Python模擬登陸某網教師教育網
本文轉載自看雪論壇【作者】rdsnow
不得不說,最近的 Python 蠻火的,我也稍稍瞭解了下,並試著用 Python 爬取網站上的資料
不過有些資料是要登陸後才能獲取的,我們每年都要到某教師教育網學習一些公需科目,就拿這個網站試試,關鍵是對網站的分析
開啟瀏覽器,輸入網站網址 http://www.jste.net.cn ,按F12調出瀏覽器的開發者工具,選中 Network ,並勾選 Preserve log,防止切換網頁時資訊丟失
網頁上輸入賬號,密碼輸入“123456”,驗證碼輸入“abcde”,驗證碼不要輸正確的,否則密碼錯5次,會被網站鎖定賬號30個小時,驗證碼倒是可以隨便錯
登陸後(當然登陸不上,會跳轉到另一個登陸頁面),在開發者工具中看到與伺服器的資料交換
第一個是get驗證碼圖片的,第二個就是向網站提交資料的,點一下第二個資訊
這是個 Post 請求,重點看紅框中的提交資料,randomCode就是輸入的驗證碼了,x,y應該是點選的按鈕控制元件的位置了,有cookie後就沒有提交這個資料了,可以忽視,returnURL、appId,encrypt每次都是一樣的,也不用管他,重點是 reqId 和 req 這兩個 key 的值了,reqId猜想是點選按鈕時取到的時間戳,可以複製這個資料到驗證下 Unix時間戳(Unix timestamp)轉換工具 單位選毫秒,確實是剛剛提交資料的時間,就剩下一個資料了,這個key的數值很長,下面來尋找這個資料是從哪裡的來的
可以看到 login.jsp 下可以看到 encode.js、string.js、des.js 從名字上就能看出這幾個是用來加密提交資料的,右鍵 login.jsp,選擇 “Open in Sources panel”
可以跳轉到 “源” 選項卡,看到 ’login.jsp‘ 的原始碼,如果格式混亂,比如所有程式碼在一行中,不便於觀看,可以點選介面下方
的中括號,開發者工具會自動給你重新格式化程式碼。
仔細分析 login.jsp 的程式碼,看到
function doOk(frm) { var el = frm.elements["loginName"]; var loginName = el.value.replace(/ /g, “”); el.value = loginName; if (isEmpty(loginName)) { alert(“請輸入登入名”); el.focus(); return false; } el = frm.elements["pwd"]; el.value = el.value.replace(/ /g, “”); var pwd=el.value; if(isEmpty(el.value)) { alert(“請輸入登入密碼”); el.focus(); return false; } var d = new Date(); pwd = encode(loginName, pwd);//密碼第一次加密,可以跟進 frm.elements["encrypt"].value = “1″; var validCode=“”; el=frm.elements["randomCode"]; if(el){ el.value=el.value.replace(/ /g,“”); if(isEmpty(el.value)) { alert(“請輸入登入密碼”); el.focus(); return false; } validCode=el.value; } loginName=encodeURI(loginName);//避免中文問題 進行URL編碼 var reqId=(new Date()).getTime()+“”;//獲取時間戳給 reqId varstr=strEnc(loginName+“\n”+pwd,reqId,validCode);//關鍵加密程式碼,可以跟進分析 frm.elements["loginName"].disabled=“true”; frm.elements["pwd"].value=pwd; frm.elements["pwd"].disabled=“true”; frm.elements["req"].value=str; frm.elements["reqId"].value=reqId; return true; }
找到這段程式碼,其中主要是對輸入檢查的部分,重點看這兩處
pwd = encode(loginName, pwd);
此處對密碼進行第一次加密
loginName=encodeURI(loginName);//避免中文問題var reqId=(new Date()).getTime()+“”;varstr=strEnc(loginName+“\n”+pwd,reqId,validCode);
第一行:將使用者名稱進行 URL 的格式編碼
第二行,取時間戳賦值給 reqId
第三行傳入使用者名稱,加密後的密碼和驗證碼進行驗證,函式返回值賦給變數 str,正是提交資料的 req 的值
在兩個加密函式入口設定斷點,開發者工具設定斷點的,只要在這個程式碼的行號上點選滑鼠就行了,設好斷點後,再次輸入使用者名稱密碼和驗證碼,重新提交,程式被斷下:
F11單步進入第一個斷點,這裡需要點選介面下面的中括號重新格式化下程式碼,單步跟進後看到:
var _$_7151 = ["encode", "ABCDEFGHIJKLMNOP", "QRSTUVWXYZabcdef", "ghijklmnopqrstuv", "wxyz0123456789+/", "=", "", "charCodeAt", "charAt", "length", "join", "reverse", "split"];window[_$_7151[0]] = function(c, e) { function a(p) { var q = _$_7151[1] + _$_7151[2] + _$_7151[3] + _$_7151[4] + _$_7151[5]; p = encodeURI(p); var r = _$_7151[6]; var g, h, j = _$_7151[6]; var k, l, m, o = _$_7151[6]; var b = 0; do { g = p[_$_7151[7]](b++);//第一個字元 h = p[_$_7151[7]](b++);//第二個字元 j = p[_$_7151[7]](b++);//第三個字元 k = g >> 2; //得到 k l = ((g & 3) << 4) | (h >> 4);//得到 i m = ((h & 15) << 2) | (j >> 6);//得到 m o = j & 63; //得到 o if (isNaN(h)) { //如果沒有第二個字元 m = o = 64 //則取表中的第64個字元替換 } else { if (isNaN(j)) { //如果沒有第三個字元 o = 64 //則取表中的第64個字元替換 } } ;r = r + q[_$_7151[8]](k) + q[_$_7151[8]](l) + q[_$_7151[8]](m) + q[_$_7151[8]](o); g = h = j = _$_7151[6]; k = l = m = o = _$_7151[6] } while (b < p[_$_7151[9]]);;return r } var d = c[_$_7151[9]]; var f = a(e)[_$_7151[12]](_$_7151[6])[_$_7151[11]]()[_$_7151[10]](_$_7151[6]); for (var b = 0; b < (d % 2 == 0 ? 1 : 2); b++) { f = a(f)[_$_7151[12]](_$_7151[6])[_$_7151[11]]()[_$_7151[10]](_$_7151[6]) } ;return f}
這個函式返回的 f 就是密碼第一次加密後的結果了,這個程式碼是用什麼工具變成這樣的不太清楚,如果出現 _$_7151[n] 這樣的字元可以查詢程式碼最上面的列表
代換,大致過程不詳說,跟一遍就知道了,就是迴圈從密碼中取三個字元 g、h、j,然後將三個字元的ascii碼左移或右移,或和其他結果加加減減,得到的結果 k、l、m、o 查詢表格替換字元,如果密碼長度不是 3 的整數倍,則查表結果用 “=” 替換,將迴圈得到的查表結果依次連線,並反序,得到一個密碼加密後的密碼
至少將密碼進行兩次這樣的加密計算,如果使用者名稱的長度是奇數,再進行一次加密,加密的過程只需要複製程式碼到 python 中,修改成 python 的格式就可以了。
步過了對密碼的第一次加密後,繼續步進上面設下的第二個斷點
function strEnc(data,firstKey,secondKey,thirdKey){ var leng = data.length;//取 data 的長度 varencData = “”; var firstKeyBt,secondKeyBt,thirdKeyBt,firstLength,secondLength,thirdLength; if(firstKey != null && firstKey != “”){ firstKeyBt = getKeyBytes(firstKey);//取 firstkey 在每個字元之間插入一個位元組的 0 firstLength = firstKeyBt.length;//取得插入 0 後的長度 } if(secondKey != null && secondKey != “”){ secondKeyBt = getKeyBytes(secondKey);//取 secondkey 在每個字元之間插入一個位元組的 0 secondLength = secondKeyBt.length;//取得插入 0 後的長度 } if(thirdKey != null && thirdKey != “”){ //登陸過程中,並沒用到 thirdkey,即 thirdKey = None thirdKeyBt = getKeyBytes(thirdKey);//取 thirdkey 在每個字元之間插入一個位元組的 0 thirdLength = thirdKeyBt.length;//取得插入 0 後的長度 } if(leng > 0){ if(leng < 4){ 如果 data 的長度<4,因為跳過,程式碼用省略號替換 //省去一些程式碼…… }else{ var iterator = parseInt(leng/4);//data 的長度除 64,得到迴圈次數 var remainder = leng%4; //data 的長度是否是 64 位的整數倍,儲存餘數 var i=0; for(i = 0;i < iterator;i++){ //開始迴圈 var tempData = data.substring(i*4+0,i*4+4); //迴圈取 data 的64 位 var tempByte = strToBt(tempData);//轉換成 bits var encByte ; if(firstKey != null && firstKey !=“” && secondKey != null && secondKey != “” ){ var tempBt; var x,y; tempBt = tempByte; for(x = 0;x < firstLength ;x ++){ tempBt = enc(tempBt,firstKeyBt[x]);//迴圈從firstkey 中取得64 位做金鑰,依次對 data 中的某一段加密 } for(y = 0;y < secondLength ;y ++){ tempBt = enc(tempBt,secondKeyBt[y]);//迴圈從second中取得64 位做金鑰,依次對 data 中的某一段加密 } encByte = tempBt;//儲存加密結果 } //………… if(remainder > 0){ //如果 data 有多餘的長度,不足64 位 var remainderData = data.substring(iterator*4+0,leng); var tempByte = strToBt(remainderData);//將餘下的分到4個16位的陣列中 var encByte ; if(firstKey != null && firstKey !=“” && secondKey != null&& secondKey != “” && thirdKey != null ){ var tempBt; var x,y,z; tempBt = tempByte; for(x = 0;x < firstLength ;x ++){ tempBt = enc(tempBt,firstKeyBt[x]);迴圈從firstkey 中取得64 位做金鑰,依次對 data 中的某一段加密 } for(y = 0;y < secondLength ;y ++){ tempBt = enc(tempBt,secondKeyBt[y]);迴圈從secondkey中取得64 位做金鑰,依次對 data 中的某一段加密 } encByte = tempBt;//儲存加密結果 } encData += bt64ToHex(encByte);//將加密後的文字轉為16進位制文字 } } } return encData;//返回加密結果}
這是一段迴圈進行 DES 加密的程式碼,先將data, firstkey, secondkey進行字元間插入一個位元組的0, 然後不是 64 位整數倍長度的從上面程式碼看,相當於在後面補上 0 了
從data中取出一段64位資料,迴圈用 firstkey 和 second 中的 64 位做金鑰,層層加密,得到的結果和 data 中其他 64 位加密的結果串聯後就是 req 的值了
因為 key 都是 64 位的,再加上本身 sources 中也看到了 DES.js 檔案,所以 enc(tempBt,secondkeyBt)應該就是 DES 演算法了。
但是自己寫程式碼模擬登陸確發現結果和自己跟的結果不同,從程式碼中看,DES 採用了 ECB 模式,不是 CBC 模式,PAD_mode 也沒問題,都64位,不需要 DES 自己填充啊。沒辦法,只得硬著頭皮繼續跟進 DES 加密的程式碼
我們知道,DES 加密需要先對 key 進行 置換,得到 56 位金鑰,標準的 DES 都有個置換表,正常的 DES 置換表是這樣的
Permutation and translation tables for DES __pc1 = [56, 48, 40, 32, 24, 16, 8, 0, 57, 49, 41, 33, 25, 17, 9, 1, 58, 50, 42, 34, 26, 18, 10, 2, 59, 51, 43, 35, 62, 54, 46, 38, 30, 22, 14, 6, 61, 53, 45, 37, 29, 21, 13, 5, 60, 52, 44, 36, 28, 20, 12, 4, 27, 19, 11, 3 ]
即將 key 的第 56 位放到第 0 位,第 48 位放到第 1 位…………最後置換出 56 位的 key,再分成 2 個28 金鑰,迴圈左移和右移,然後 對 IP 置換後的 data 加密,進行 Sbox 盒替換 和 Pbox 替換,再進行一次 IP-1 置換得到密文,解密演算法一樣。
但跟進 DES 加密函式沒多久就發現問題了,找到金鑰置換的函式
var keys = generateKeys(keyByte);
並跟進:
function generateKeys(keyByte){ var key = new Array(56); var keys = new Array(); keys[ 0] = newArray(); keys[ 1] = new Array(); keys[ 2] = new Array(); keys[ 3] = new Array(); keys[ 4] = newArray(); keys[ 5] = new Array(); keys[ 6] = new Array(); keys[ 7] = new Array(); keys[ 8] = newArray(); keys[ 9] = new Array(); keys[10] = new Array(); keys[11] = new Array(); keys[12] = newArray(); keys[13] = new Array(); keys[14] = new Array(); keys[15] = new Array(); var loop = [1,1,2,2,2,2,2,2,1,2,2,2,2,2,2,1];//看到了迴圈移位的表,沒看到置換表 for(i=0;i<7;i++){ for(j=0,k=7;j<8;j++,k–){ key[i*8+j]=keyByte[8*k+i];//用了這個迴圈生成 56 位 } }//省略程式碼}
這裡修改了標準的置換表,用了一個巢狀迴圈生成 56 位金鑰,即把
原來 key 的 56 位 –> 第 0 位,48 位 –> 第 1 位,40 位 –> 第 2 位,…………0 位–> 第 7 位
原來 key 的 57 位 –> 第 8 位,49 位 –> 第 9 位,41 位 –> 第 10 位,………… 1 位 –>第 15 位
…………
最後丟棄原 key 的第 63,55,47,39,31,23,15,7 位(位置號從 0 開始)
在 python 中不能直接使用標準的 DES庫了,可以把標準庫中的 pyDes.py 檔案拷貝到工程同目錄下,改名為 Des,py,並匯入工程
from Des import *
另外在 Des.py 中找到 key 的置換表,修改成
__pc1 = [ 56, 48, 40, 32, 24, 16, 8, 0, 57, 49, 41, 33, 25, 17, 9, 1, 58, 50, 42, 34, 26, 18, 10, 2, 59, 51, 43, 35, 27, 19, 11, 3, 60, 52, 44, 36, 28, 20, 12, 4, 61, 53, 45, 37, 29, 21, 13, 5, 62, 54, 46, 38, 30, 22, 14, 6 ]
就可以正常使用 Des 了
最後附上 python 程式碼:
from Des import *from urllib.parse import quotefrom time import time, sleepfrom PIL importImageimport requestsimport sysfrom bs4 import BeautifulSoups = requests.session()headers = { ‘Cache-Control’: ‘max-age=0′, ‘Connection’: ‘keep-alive’, ‘Referer’: ‘http://www.jste.net.cn/uids/login.jsp‘, ‘User-Agent’: ‘Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) \ Chrome/58.0.3029.110 Safari/537.36 SE 2.X MetaSr 1.0′}def custom_encode(data): # 懶得註釋了,直接從js中拷貝出來,改成python的程式碼 tab = ‘ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=’ data_bytes = list(data.encode()) while len(data_bytes) % 3 != 0: data_bytes.append(0) b = 0 length = len(data_bytes) r = ” while b < length: g = data_bytes[b] h = data_bytes[b + 1] j = data_bytes[b + 2] k = g >> 2 m = ((g & 3) << 4) | (h >> 4) n = ((h & 15) << 2) | (j >> 6) o = j & 63 third_char = ‘=’ if h == 0 else tab[n] fourth_char = ‘=’ if j == 0 else tab[o] r = r + tab[k] + tab[m] + third_char + fourth_char b = b + 3 return r[::-1] # 反序輸出def encode_pwd(str_name, str_pwd): encoded_pwd = custom_encode(str_pwd) encoded_pwd = custom_encode(encoded_pwd) # 先連續對密碼加密兩次 if len(str_name) % 2 == 1: encoded_pwd = custom_encode(encoded_pwd) # 如果使用者名稱長度是奇數,則再加密一次 return encoded_pwddef strenc(data, firstkey, secondkey): bts_data = extend_to_16bits(data) # 將data長度擴充套件成64位的倍數 bts_firstkey = extend_to_16bits(firstkey) # 將 first_key 長度擴充套件成64位的倍數 bts_secondkey = extend_to_16bits(secondkey) # 將 second_key 長度擴充套件成64位的倍數 i = 0 bts_result = [] while i < len(bts_data): bts_temp = bts_data[i:i + 8] # 將data分成每64位一段,分段加密 j, k = 0, 0 while j < len(bts_firstkey): des_k = des(bts_firstkey[j: j + 8], ECB) # 分別取出 first_key 的64位作為金鑰 bts_temp = list(des_k.encrypt(bts_temp)) j += 8 while k < len(bts_secondkey): des_k = des(bts_secondkey[k:k + 8], ECB) # 分別取出 second_key 的64位作為金鑰 bts_temp = list(des_k.encrypt(bts_temp)) k += 8 bts_result.extend(bts_temp) i += 8 str_result = ” for each in bts_result: str_result += ‘%02X’ % each # 分別加密data的各段,串聯成字串 returnstr_resultdef extend_to_16bits(data): # 將字串的每個字元前插入 0,變成16位,並在後面補0,使其長度是64位整數倍 bts = data.encode() filled_bts = [] for each in bts: filled_bts.extend([0, each]) # 每個字元前插入 0 while len(filled_bts) % 8 != 0: # 長度擴充套件到8的倍數 filled_bts.append(0) # 不是8的倍數,後面新增0,便於DES加密時分組 return filled_btsdef get_rand_code(): random_code_url = r’http://www.jste.net.cn/uids/genImageCode?rnd=‘ time_stamp = str(int(time() * 1000)) random_code_url += time_stamp try: req = s.get(random_code_url, headers=headers, stream=True) with open(‘rand_code.jpg’, ‘wb’) as f: for chunk inreq.iter_content(chunk_size=1024): f.write(chunk) except requests.RequestException: print(‘網路連結錯誤,請稍後重試/(ㄒoㄒ)/~~’) sys.exit() with Image.open(‘rand_code.jpg’)asimg: img.show()def login_site(reqid, randomcode, reqkey): post_data = { ‘randomCode’: randomcode, ‘returnURL’: None, ‘appId’: ‘uids’, ‘site’: None, ‘encrypt’: 1, ‘reqId’: reqid, ‘req’: reqkey } try: req = s.post(‘http://www.jste.net.cn/uids/login.jsp‘, headers=headers, data=post_data) print(‘Status Code:%s’ % req.status_code) # 不知道為什麼瀏覽器上登陸成功返回的是302,這裡返回200 if ‘Set-Cookie’ in req.headers.keys(): # 還好,看到response中出現Set-Cookie,就登陸成功了 returnTrue else: return False except requests.RequestException: print(‘網路連結錯誤,請稍後重試/(ㄒoㄒ)/~~’) return Falsedef main(): print(”.center(100, ‘-’)) uname = input(‘請輸入你的使用者名稱:’) pwd = input(‘請輸入你的登陸密碼:’) get_rand_code() secondkey = input(‘請輸入看到的驗證碼:’) # 取得驗證碼,作為second_key,提交資料時作為 randomCode 的值 firstkey = str(int(time() * 1000)) # 取得提交時的時間戳,作為first_key,提交資料時候作為 reqId 的值 crypt_pwd = encode_pwd(uname, pwd) # 對輸入的密碼進行第一次加密 data = quote(uname) + ‘\n’ + crypt_pwd # 使用者名稱URI編碼後和密碼加密後的文字連結等待被DES加密 post_req = strenc(data, firstkey, secondkey) # 主要是DES計算,作為 req 的值提交資料 if login_site(reqid=firstkey, randomcode=secondkey, reqkey=post_req) is True: print(”.center(100, ‘-’)) print(‘登陸成功,O(∩_∩)O哈哈~…’) try: req = s.get(‘http://www.jste.net.cn/train/credit_hour/top.jsp‘) # 開啟一個網頁測試一下 soup = BeautifulSoup(req.text, ‘html5lib’) # 網頁為多框架,測試下訪問TOP框架中的文字 print(soup.select(‘.b’)[0].text.replace(‘\n’, ”).replace(‘ ‘, ”)) exceptrequests.RequestException: print(‘網路連結錯誤,請稍後重試/(ㄒoㄒ)/~~’)if __name__ == ‘__main__’: # 啟動程式 main()
測試效果:
最後思考了下,很多網站的資料都是明碼提交的,或者是簡單的加密提交的,這個網站在加密上花了一些工夫
但是js指令碼最大的問題就是別人可以看到原始碼,雖然網站登陸成功後立即刪除了js檔案,但是隻要出現了就會被發現,我網上搜尋了下隱藏原始碼的辦法,但是水平才菜了,沒學過 java ,也沒看懂。
最後補充下:DES加密的資料 data 是使用者名稱的” URL格式 + 換行 + 密碼第一次加密得到的文字“
firstkey 是提交時得到的時間戳,secondkey 就是輸入的驗證碼
相關文章
- JS逆向實戰26——某店ua模擬登陸JS
- 通過抓包實現Python模擬登陸各網站原理分析Python網站
- Python模擬微博登陸,親測有效Python
- 網站模擬登陸的滑塊驗證碼識別網站
- python 爬蟲網頁登陸Python爬蟲網頁
- 通過session模擬登陸Session
- 使用OkHttp模擬登陸LeetCodeHTTPLeetCode
- 模擬部落格園登陸!
- 模擬登陸——以github為例Github
- 爬蟲之普通的模擬登陸爬蟲
- Python-模擬登入Python
- 使用postman模擬登陸post請求方法Postman
- 使用Selenium模擬登陸百度盤
- Python+Selenium+phantomjs實現網頁模擬登入和截圖PythonJS網頁
- 【Python】python程式碼來登陸網站簽到米粒VPNPython網站
- 利用Python模擬GitHub登入PythonGithub
- Python系列模擬登入之網易雲個人歌單下載器Python
- 用不同的庫實現模擬登陸知乎!
- 《巴士模擬21》現已登陸PC、PlayStation®和Xbox!
- 如何通過Python暴力破解網站登陸密碼Python網站密碼
- 為何無法登陸某一網頁怎麼辦_電腦瀏覽器無法登入某個網站解決方法網頁瀏覽器網站
- python ftp登陸(匿名)PythonFTP
- Cisco Packet Tracer Student(思科網路模擬器)模擬搭建VLAN網路
- Python 爬蟲模擬登入方法彙總Python爬蟲
- 如何登陸網站後臺伺服器網站伺服器
- 有道精品課教師節專場,教師家族走出一名網課老師
- 天才模擬器程式設計師死於網路暴力中程式設計師
- 科幻模擬潛艇遊戲《潛淵症Barotrauma》現已登陸STEAM!遊戲
- 模擬城市建設類VR遊戲 《迷你都市》登陸PICO平臺VR遊戲
- 紅警教育陸續推出多套資料恢復國賽模擬考題資料恢復
- IdentityServer4網頁(單點)登陸入門IDEServer網頁
- SpringBoot之網站的登陸註冊邏輯Spring Boot網站
- Charles 模擬弱網測試
- ---ios模擬弱網測試---iOS
- Python爬蟲實戰之(五)| 模擬登入wechatPython爬蟲
- 安服仔日記系列——某教育局內網被打穿內網
- selenium模擬登入12306
- 登陸公司網站顯示亂碼怎麼辦?網站